SECTOR 03 // REFERENCE

http

Request and capture over HTTP, with structured JSON responses and status assertions.

Request and capture. The primitive composed domain providers build on.

providers:
  http:
    config:
      baseUrl: http://localhost:8080   # alias: apiBase
      basicAuth: { username: admin, password: "${.env.PASSWORD}" }   # optional

baseUrl (alias apiBase) is prepended to each step’s path. A step may pass an absolute URL in path to override it. basicAuth (optional) applies HTTP Basic credentials to every request; a per-step basicAuth: overrides it, and an explicit Authorization header overrides both.

Verbs #

path is the primary arg on every verb, so the scalar shorthand with: /health is the path. Every verb shares the same response envelope (see Response) and status rules (see Status handling).

get (probe) #

Reads a resource. A probe, so it re-runs during steadyState recovery and counts as an observation.

argtypereqdescription
pathstringyesrequest path, appended to baseUrl; an absolute URL overrides it (primary)
headersmapnorequest headers
basicAuthmapno{ username, password }, overriding the provider-level credentials
expectStatuslistnostatus codes to tolerate instead of failing (see Status handling)

Returns the response envelope: the decoded JSON (or raw string) in value, the raw body in output, and meta.status / meta.bytes. See Response.

- run: http.get
  with: /health
  as: rsp
- run: assert
  with: { of: "${.outputs.rsp.meta.status}", equals: 200 }

head (probe) #

get’s cheap sibling: status and headers, no body. The reachability probe for a large resource, where the report should never drag the payload along; meta.status is all it keeps.

Same args as get. Returns the response envelope with an empty value and output; meta.status carries the signal.

- run: http.head
  with: /exports/latest.csv
  as: rsp
- run: assert
  with: { of: "${.outputs.rsp.meta.status}", equals: 200 }

post / put / patch / delete (action) #

Writes a resource: patch for a partial update, the others full-resource. Actions, so they are skipped on --dry-run. The request body comes from body, raw, or form (precedence rawbodyform); see Request for the full rules.

argtypereqdescription
pathstringyesrequest path, appended to baseUrl; an absolute URL overrides it (primary)
bodymapnoJSON-encoded request body
rawstringnoverbatim request body, no encoding
contentTypestringnooverrides the Content-Type the body type implies
formmapnoURL-encoded form body
headersmapnorequest headers
basicAuthmapno{ username, password }, overriding the provider-level credentials
expectStatuslistnostatus codes to tolerate instead of failing (see Status handling)

Returns the same response envelope as get. See Response.

- run: http.post
  with: /orders
  body: { item: "sku-42", qty: 2 }
  as: order

Request #

The body precedence is rawbodyform.

- run: http.post
  with: /orders
  body: { item: "sku-42", qty: 2 }
  headers: { Authorization: "Bearer ${.env.TOKEN}" }
  as: order

- run: http.post                         # deploy a raw YAML document
  with: /flows
  raw: "${.outputs.flow_yaml}"
  contentType: application/x-yaml

Response #

When a step binds the result with as: name, the whole response is available under .outputs.name as an envelope of three keys:

fieldwhat it holds
valuethe parsed payload: the JSON-decoded structure when the response Content-Type contains json and parses, otherwise the raw body as a string
outputthe raw response body as a string, always (for logs and diagnostics)
metastatus (int), bytes (response length), and durationMs (the engine stamps this on every call)
- run: http.get
  with: /orders/${.outputs.order.value.id}
  as: rsp
- run: assert
  with: { of: "${.outputs.rsp.meta.status}", equals: 200 }
- run: assert
  with: { of: "${.outputs.rsp.value.state}", equals: "confirmed" }
- run: assert
  with: { of: "${.outputs.rsp.meta.durationMs}", lt: 200 }

read: and capture: operate on the payload (value), so their jq expressions address the decoded JSON directly through .. The envelope’s other two keys are bound as jq variables: $meta ($meta.status, $meta.bytes, $meta.durationMs) and $output (the raw body). This lets a check gate on the status code without first binding the whole envelope with as:. as: binds the full envelope; the three compose:

- run: http.get
  with: /orders
  read: "[.[] | select(.state == \"pending\")] | length"   # jq over value
  as: pending                                               # pending.value is the count

- run: wait_until                          # readiness that tolerates 401/403
  with:
    probe: { run: http.get, with: { path: /health, expectStatus: [200, 401, 403] } }
    read: "$meta.status"
    in: [200, 401, 403]
    timeout: 30

Status handling #

A status < 400 succeeds. A status >= 400 is a step failure (the message carries the status and a truncated body) unless that code is listed in expectStatus, in which case the step returns normally with the status in meta. List the codes you want to tolerate to observe graceful degradation:

- run: http.get
  with: /checkout
  expectStatus: [200, 503]   # 503 is an acceptable degraded response, not a failure
  as: rsp
- run: assert
  with: { of: "${.outputs.rsp.meta.status}", in: [200, 503] }

A request that never completes (connection refused, DNS failure, timeout) is a step error rather than a status failure. Each request has a 30s default timeout; a per-step timeout: of any value is authoritative and overrides it.