Skip to main content

Workflow: Public API Type

A Public API workflow exposes custom REST endpoints that external systems, partners, or frontend applications can call. Each workflow defines a single HTTP endpoint with its own path, method, authentication, rate limiting, and business logic.

Public API workflows are ideal when you need to:

  • Expose data or actions to external partners without giving them full system access
  • Build custom REST APIs backed by workflow logic (queries, transformations, integrations)
  • Create webhooks for third-party services with input validation and typed parameters
  • Serve dynamic content from your TMS data for portals or mobile apps

Base URL

All Public API endpoints are served under:

/public-api/v1/{orgUniqueId}/{path}

Where {orgUniqueId} is your organization's unique identifier (UUID format).

Swagger / OpenAPI Documentation

Each organization gets auto-generated Swagger documentation based on its active Public API workflows.

Swagger Endpoints

URLDescription
/public-api/v1/{orgUniqueId}/swagger/Lists all available API document groups
/public-api/v1/{orgUniqueId}/swagger/{documentName}Interactive Swagger UI for a document group
/public-api/v1/{orgUniqueId}/swagger/{documentName}/swagger.jsonOpenAPI 3.0 JSON specification

Document Groups

Workflows are grouped into Swagger documents using the api.document property. If not specified, workflows default to the "public" document group.

api:
document: "partners" # This endpoint appears in the "partners" Swagger doc

To view the partners Swagger UI, navigate to:

/public-api/v1/{orgUniqueId}/swagger/partners

YAML Structure

A Public API workflow uses the standard workflow manifest with an additional api section:

api:
path: "/your/endpoint/{param}"
method: "GET"
authentication: "none"
# ... additional API config

workflow:
name: "My Public Endpoint"
workflowId: "00000000-0000-0000-0000-000000000000"
workflowType: "PublicApi"
executionMode: "Sync" # Required: must be Sync
isActive: true

inputs:
- name: "param"
type: "string"
props:
in: "path"
required: true

outputs:
- name: "response" # Required
mapping: "stepName.result"
- name: "statusCode" # Optional
mapping: "stepName.code"

activities:
- name: myActivity
steps:
- task: "SomeTask@1"
name: "stepName"
# ...

API Configuration

The api section defines the HTTP endpoint configuration.

PropertyTypeRequiredDefaultDescription
pathstringYes-URL path (must start with /). Supports path parameters with {paramName} syntax
methodstringYes-HTTP method: GET, POST, PUT, PATCH, or DELETE
authenticationstringNobearerAuthentication method: none, bearer, or apiKey
summarystringNo-Short summary shown in Swagger UI
descriptionstringNo-Detailed description shown in Swagger UI
operationIdstringNoauto-generatedUnique operation ID for the OpenAPI spec
documentstringNopublicSwagger document group name
categorystringNo-Category tag for grouping endpoints in Swagger UI
rateLimitobjectNo-Rate limiting configuration
rateLimit.perSecondnumberNo0Max requests per second per client IP
rateLimit.perMinutenumberNo0Max requests per minute per client IP
timeoutnumberNo60Workflow execution timeout in seconds
maxBodySizenumberNo1048576Maximum request body size in bytes (default 1MB)

Authentication

Public API workflows support three authentication methods:

No Authentication

api:
authentication: "none"

The endpoint is publicly accessible without any credentials.

Bearer Token (Default)

api:
authentication: "bearer"

Requires a valid JWT Bearer token in the Authorization header:

Authorization: Bearer eyJhbGciOiJSUzI1NiIs...

If authentication is omitted, bearer is used by default.

API Key

api:
authentication: "apiKey"

Requires the X-API-Key header:

X-API-Key: your-api-key-here

The API key value is passed to the workflow as a header input. Your workflow is responsible for validating the key (e.g., by looking it up in a configuration or database).

Inputs

Inputs define the parameters your endpoint accepts. Each input specifies where the value comes from using the props.in property.

Input Locations

props.inSourceDescription
pathURL pathExtracted from path parameters (e.g., {orderId} in /orders/{orderId})
queryQuery stringExtracted from URL query parameters (e.g., ?status=active)
headerHTTP headersExtracted from request headers
bodyRequest bodyParsed from the JSON request body. Only one body input is allowed per endpoint

Input Types

TypeDescription
stringText value (default)
integer / intInteger number
number / decimal / float / doubleDecimal number
boolean / boolBoolean value
objectJSON object (for body inputs)

Input Props

PropertyTypeRequiredDescription
instringYesInput location: path, query, header, or body
requiredbooleanNoWhether the parameter is required. Path parameters are always required
descriptionstringNoDescription shown in Swagger UI
formatstringNoFormat hint for Swagger UI (e.g., uuid, date-time)

Input Example

inputs:
- name: "orderId"
type: "integer"
props:
in: "path"
required: true
description: "The order ID"

- name: "includeDetails"
type: "boolean"
props:
in: "query"
required: false
description: "Include full order details"

- name: "X-API-Key"
type: "string"
props:
in: "header"
required: true
description: "API key for authentication"

- name: "payload"
type: "object"
props:
in: "body"
required: true
description: "Request payload"

Outputs

Public API workflows must define a response output. An optional statusCode output controls the HTTP status code.

Output NameRequiredDescription
responseYesThe response body returned to the caller. Can be any JSON-serializable value
statusCodeNoHTTP status code (integer). Defaults to 200 if not set
outputs:
- name: "response"
mapping: "fetchData.result"
- name: "statusCode"
mapping: "fetchData.code"

Request Metadata

In addition to the defined inputs, the workflow receives a request variable with HTTP request metadata:

PropertyDescription
request.headersDictionary of all HTTP request headers
request.bodyRaw request body as a string
request.methodHTTP method (GET, POST, etc.)
request.pathNormalized request path
request.remoteIpAddressClient IP address

Rate Limiting

Rate limiting is per-client-IP using a sliding window algorithm. Configure it in the api.rateLimit section:

api:
rateLimit:
perSecond: 10
perMinute: 100

When the rate limit is exceeded, the endpoint returns 429 Too Many Requests.

Client IP is resolved from (in order): CF-Connecting-IP, X-Forwarded-For, X-Real-IP, or the connection's remote IP.

Error Responses

Status CodeCondition
200Success (or custom code via statusCode output)
400Input validation failed (missing required params, type mismatch)
401Authentication failed (missing or invalid credentials)
404Organization not found or no matching endpoint
413Request body exceeds maxBodySize
429Rate limit exceeded
504Workflow execution timed out
500Unexpected server error

Error response format:

{
"error": "Description of the error."
}

For input validation errors:

{
"error": "Input validation failed.",
"details": {
"orderId": "Required parameter 'orderId' is missing.",
"quantity": "Cannot convert 'abc' to integer."
}
}

Validation Rules

Public API workflows are validated at save time. The following rules apply:

CodeRule
PAPI_001api section is required
PAPI_002api.path is required
PAPI_003api.method is required
PAPI_004api.method must be GET, POST, PUT, PATCH, or DELETE
PAPI_005api.authentication must be none, bearer, or apiKey (if provided)
PAPI_006executionMode must be Sync
PAPI_007Every {param} in the path must have a matching input with in: path
PAPI_008At most one input can have in: body
PAPI_010Path must start with / and must not contain ..
PAPI_011Outputs must include a response output
PAPI_012Warning: body input on GET or DELETE methods

Examples

Example 1: GET Endpoint - Fetch Order by ID

A simple GET endpoint that retrieves an order by ID and returns it as JSON.

api:
path: "/orders/{orderId}"
method: "GET"
summary: "Get order by ID"
description: "Returns order details for the given order ID"
operationId: "getOrderById"
document: "partners"
category: "Orders"
authentication: "apiKey"
rateLimit:
perSecond: 10
perMinute: 100

workflow:
name: "Public API / Get Order"
workflowId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
workflowType: "PublicApi"
executionMode: "Sync"
isActive: true

inputs:
- name: "orderId"
type: "integer"
props:
in: "path"
required: true
description: "Order ID"
- name: "X-API-Key"
type: "string"
props:
in: "header"
required: true
description: "Partner API key"

outputs:
- name: "response"
mapping: "fetchOrder.result"
- name: "statusCode"
mapping: "checkAuth.statusCode"

activities:
- name: validateKey
steps:
- task: "Utilities/SetVariable@1"
name: "checkAuth"
inputs:
variables:
- name: "statusCode"
expression: "if([X-API-Key] = '{{ partnerApiKey }}', null, 401)"
- name: "authError"
expression: "if([X-API-Key] = '{{ partnerApiKey }}', null, 'Invalid API key')"

- name: fetchOrderData
conditions:
- expression: "[checkAuth.statusCode] = null"
steps:
- task: "Query/GraphQL@1"
name: "fetchOrder"
inputs:
query: |
query {
order(orderId: {{ orderId }}, organizationId: {{ organizationId }}) {
orderId
orderNumber
orderDate
orderStatus { orderStatusName }
customer { name, email }
shipper { contact { name } }
consignee { contact { name } }
}
}
outputs:
- name: "result"
mapping: "order"

Example request:

curl -X GET "https://your-domain.com/public-api/v1/{orgUniqueId}/orders/12345" \
-H "X-API-Key: your-partner-key"

Example response (200):

{
"orderId": 12345,
"orderNumber": "ORD-2025-0001",
"orderDate": "2025-01-15T10:30:00Z",
"orderStatus": {
"orderStatusName": "In Transit"
},
"customer": {
"name": "Acme Corp",
"email": "logistics@acme.com"
}
}

Example 2: POST Endpoint - Create Tracking Event

A POST endpoint that accepts a JSON payload to create a tracking event for an order.

api:
path: "/orders/{orderId}/tracking"
method: "POST"
summary: "Create tracking event"
description: "Submit a tracking event for the specified order"
document: "partners"
category: "Tracking"
authentication: "apiKey"
rateLimit:
perSecond: 5
perMinute: 60
timeout: 30
maxBodySize: 65536 # 64KB

workflow:
name: "Public API / Create Tracking Event"
workflowId: "b2c3d4e5-f6a7-8901-bcde-f23456789012"
workflowType: "PublicApi"
executionMode: "Sync"
isActive: true

inputs:
- name: "orderId"
type: "integer"
props:
in: "path"
required: true
description: "Order ID"
- name: "payload"
type: "object"
props:
in: "body"
required: true
description: "Tracking event data"

outputs:
- name: "response"
mapping: "buildResponse.result"
- name: "statusCode"
mapping: "buildResponse.code"

variables:
- name: "eventCode"
value: "{{ payload.eventCode }}"
- name: "eventDate"
value: "{{ payload.eventDate }}"
- name: "description"
value: "{{ payload.description }}"
- name: "location"
value: "{{ payload.location }}"

activities:
- name: createEvent
steps:
- task: "TrackingEvent/Create@1"
name: "createTracking"
inputs:
orderId: "{{ orderId }}"
eventCode: "{{ eventCode }}"
eventDate: "{{ eventDate }}"
description: "{{ description }}"
location: "{{ location }}"
outputs:
- name: "trackingEventId"

- task: "Utilities/SetVariable@1"
name: "buildResponse"
inputs:
variables:
- name: "result"
value:
message: "Tracking event created successfully"
trackingEventId: "{{ createTracking.trackingEventId }}"
- name: "code"
value: 201

Example request:

curl -X POST "https://your-domain.com/public-api/v1/{orgUniqueId}/orders/12345/tracking" \
-H "X-API-Key: your-partner-key" \
-H "Content-Type: application/json" \
-d '{
"eventCode": "DELIVERED",
"eventDate": "2025-06-15T14:30:00Z",
"description": "Package delivered to front door",
"location": "New York, NY"
}'

Example response (201):

{
"message": "Tracking event created successfully",
"trackingEventId": 98765
}

Example 3: GET Endpoint with Query Parameters

A list endpoint with pagination and filtering via query parameters.

api:
path: "/orders"
method: "GET"
summary: "List orders"
description: "Returns a paginated list of orders with optional filtering"
document: "partners"
category: "Orders"
authentication: "bearer"
rateLimit:
perSecond: 5
perMinute: 60

workflow:
name: "Public API / List Orders"
workflowId: "c3d4e5f6-a7b8-9012-cdef-345678901234"
workflowType: "PublicApi"
executionMode: "Sync"
isActive: true

inputs:
- name: "status"
type: "string"
props:
in: "query"
required: false
description: "Filter by order status name"
- name: "page"
type: "integer"
props:
in: "query"
required: false
description: "Page number (default: 1)"
- name: "pageSize"
type: "integer"
props:
in: "query"
required: false
description: "Page size (default: 20, max: 100)"

outputs:
- name: "response"
mapping: "fetchOrders.result"

activities:
- name: queryOrders
steps:
- task: "Query/GraphQL@1"
name: "fetchOrders"
inputs:
query: |
query {
orders(
organizationId: {{ organizationId }},
filter: { statusName: "{{ status? }}" },
skip: {{ (page? ?? 1 - 1) * (pageSize? ?? 20) }},
take: {{ pageSize? ?? 20 }}
) {
items {
orderId
orderNumber
orderDate
orderStatus { orderStatusName }
}
totalCount
}
}
outputs:
- name: "result"
mapping: "orders"

Example request:

curl -X GET "https://your-domain.com/public-api/v1/{orgUniqueId}/orders?status=In%20Transit&page=1&pageSize=10" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."

Example 4: Public Endpoint (No Authentication)

A public endpoint that returns tracking information without requiring authentication.

api:
path: "/track/{trackingNumber}"
method: "GET"
summary: "Track shipment"
description: "Public tracking page - returns shipment status by tracking number"
document: "public"
category: "Tracking"
authentication: "none"
rateLimit:
perSecond: 20
perMinute: 200

workflow:
name: "Public API / Track Shipment"
workflowId: "d4e5f6a7-b8c9-0123-def0-456789012345"
workflowType: "PublicApi"
executionMode: "Sync"
isActive: true

inputs:
- name: "trackingNumber"
type: "string"
props:
in: "path"
required: true
description: "Shipment tracking number"

outputs:
- name: "response"
mapping: "buildResult.result"
- name: "statusCode"
mapping: "buildResult.code"

activities:
- name: lookup
steps:
- task: "Query/GraphQL@1"
name: "findOrder"
inputs:
query: |
query {
orders(
organizationId: {{ organizationId }},
filter: { trackingNumber: "{{ trackingNumber }}" },
take: 1
) {
items {
trackingNumber
orderStatus { orderStatusName }
orderEvents {
trackingEvent {
eventDate
eventDefinition { eventCode, description }
location
}
}
}
}
}
outputs:
- name: "orders"
mapping: "orders.items"

- task: "Utilities/SetVariable@1"
name: "buildResult"
inputs:
variables:
- name: "result"
expression: "if(count([findOrder.orders]) > 0, elementAt([findOrder.orders], 0), null)"
- name: "code"
expression: "if(count([findOrder.orders]) > 0, 200, 404)"

Route Matching

Routes are matched using the following rules:

  1. Method matching: The HTTP method must match exactly (case-insensitive)
  2. Path matching: Path templates like /orders/{orderId}/items/{itemId} are converted to regex patterns with named capture groups
  3. Specificity: Routes with more static segments take priority over parameterized routes. For example, /orders/recent matches before /orders/{orderId}
  4. First match wins: When multiple routes match at the same specificity level, the first match is returned

Best Practices

  • Use meaningful paths that follow REST conventions (e.g., /orders/{orderId}, /orders/{orderId}/tracking)
  • Group related endpoints using the same document value for organized Swagger documentation
  • Set appropriate rate limits to protect your system from abuse
  • Use apiKey authentication for partner integrations where you control key distribution
  • Use none authentication only for truly public endpoints (tracking pages, status checks)
  • Always validate inputs using required fields and type constraints
  • Set a reasonable timeout based on the expected workflow execution time
  • Return meaningful status codes via the statusCode output (201 for creates, 404 for not found, etc.)