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:

ModeBehavior
syncThe invocation response carries the result. The default
asyncThe 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

HeaderPurpose
Argyll-Flow-IDFlow identity and correlation
Argyll-Step-IDStep identity
Argyll-Receipt-TokenWork Item identity and idempotency key
Argyll-Webhook-URLCallback 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" }
  }
}
  1. Argyll sends the request with the Argyll-Webhook-URL and Argyll-Receipt-Token headers.
  2. The service accepts the work with a 2xx response.
  3. 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:

  1. Argyll sends the compensation request.
  2. The service returns 2xx to acknowledge that rollback has started.
  3. The worker posts to the supplied URL when rollback finishes. An empty body reports success, and application/problem+json reports 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.