Service Steps
Register an HTTP endpoint as a Step, choose how it reports its result, and complete long-running work through a callback.
Invocation
Each HTTP action chooses how it reports its result:
| Mode | Behavior |
|---|---|
sync | The invocation response carries the result. The default |
async | The service accepts the work, then reports the result through a callback |
Invocation and compensation choose their modes independently.
This Step omits mode, so its invocation is synchronous:
{
"id": "create-order",
"name": "Create Order",
"type": "service",
"http": {
"invoke": {
"method": "POST",
"endpoint": "https://api.example.com/customers/{customer_id}/orders",
"timeout": 5000
}
},
"attributes": {
"customer_id": {"role": "required", "type": "string"},
"items": {"role": "required", "type": "array"},
"order_id": {"role": "output", "type": "string"}
}
}
The method is GET, POST, PUT, or DELETE, and defaults to POST. Argyll resolves {customer_id} from the Step’s invocation names, and each placeholder names a required input. POST and PUT send every input as one JSON object whose keys are those invocation names, including the ones a placeholder already consumed:
{
"customer_id": "cust-456",
"items": [
{ "sku": "ABC", "quantity": 2 }
]
}
GET and DELETE send an empty body, so they carry their data in the endpoint placeholders. A successful synchronous response returns the outputs in the same direct form, or an empty body when the Step has none:
{
"order_id": "ord-123"
}
An Attribute’s optional mapping.name controls its invocation name when the service’s field name differs from the name used in Flow state.
Execution Headers
| Header | Purpose |
|---|---|
Argyll-Flow-ID | Flow identity and correlation |
Argyll-Step-ID | Step identity |
Argyll-Receipt-Token | Work Item identity and idempotency key |
Argyll-Webhook-URL | Callback URL, sent for async actions |
Invocation and compensation of one Work Item carry the same receipt token.
Failures
Return a non-2xx status with application/problem+json:
{
"type": "about:blank",
"title": "Unprocessable Entity",
"status": 422,
"detail": "Customer not found"
}
Argyll treats 5xx and transport failures as retryable, and every other non-2xx status as permanent. Retries covers attempts and backoff.
Async Callbacks
Set "mode": "async" on an action to start long-running or queue-backed work and complete it later. The Step keeps the same HTTP and Attribute contract as a synchronous one:
{
"id": "process-payment",
"name": "Process Payment",
"type": "service",
"http": {
"invoke": {
"method": "POST",
"endpoint": "https://payments.example.com/process",
"timeout": 5000,
"mode": "async"
}
},
"attributes": {
"order_id": { "role": "required", "type": "string" },
"transaction_id": { "role": "output", "type": "string" }
}
}
- Argyll sends the request with the
Argyll-Webhook-URLandArgyll-Receipt-Tokenheaders. - The service accepts the work with a 2xx response.
- A worker posts the result to the webhook URL.
The URL has the form /callbacks/{flow_id}/{step_id}/{token}/invoke below WEBHOOK_BASE_URL. Post to the URL exactly as the header supplies it.
The action’s timeout, or STEP_TIMEOUT when the Step sets none, bounds the whole attempt from the initial request through the callback. When the deadline passes first, the Work Item becomes not_completed and retries under the Step’s retry settings. Retries keep the same receipt token, so a callback that arrives later still settles the Work Item.
Complete Work
Post output Attributes directly, or {} when the Step has none:
{
"transaction_id": "txn-abc123"
}
Post application/problem+json to report a permanent failure:
{
"type": "about:blank",
"title": "Payment Declined",
"status": 422,
"detail": "Card was declined"
}
The receipt token and action identify one callback. Repeating a callback for settled work is safe, and the engine answers 200. A callback naming an unknown Flow, Step, or token receives 400. Workers can therefore retry network and 5xx failures until they receive success, while treating 4xx as permanent.
Compensation Endpoint
A Step with compensated handling declares http.compensate beside http.invoke. The compensation call receives only the Attributes marked compensated, under their invocation names. POST and PUT send them as one JSON object:
{
"charge_id": "ch_abc123"
}
GET and DELETE resolve the same values into endpoint placeholders, each of which names a compensated Attribute. Every method receives Argyll-Flow-ID, Argyll-Step-ID, and Argyll-Receipt-Token. Use the receipt token as the endpoint’s idempotency key.
A synchronous compensation returns 2xx on success, and its response body is ignored. Failures follow the same status rules as invocation: 5xx and transport failures retry, and every other non-2xx status is permanent.
An async compensation receives an Argyll-Webhook-URL ending in /compensate:
- Argyll sends the compensation request.
- The service returns 2xx to acknowledge that rollback has started.
- The worker posts to the supplied URL when rollback finishes. An empty body reports success, and
application/problem+jsonreports permanent failure.
The compensation timeout bounds the whole attempt, through the callback. When the deadline passes first, Argyll schedules a compensation retry. Compensation covers configuration, rollback scope, and ordering.
Health Checks
A Service Step can declare a health check endpoint:
{
"http": {
"invoke": { "endpoint": "https://api.example.com/process" },
"health": "https://api.example.com/health"
}
}
Each node polls the endpoint with GET every 30 seconds, allowing 3 seconds for a response. A transport error or a 4xx or 5xx response marks the Step unhealthy on that node. Invocation outcomes continue to determine execution success.
A node holds pending Work Items for a Step it sees as unhealthy and checks again every second, dispatching once the Step recovers. Memoized cache hits complete regardless of health. Script Steps report their own health instead.