Skip to content

Ironfang Rig

Developer documentation

Give the application under test a disposable external world: temporary inbox addresses, public callback URLs, mock HTTP endpoints and routes to your machine, allocated fresh for every run, with a sequenced record of everything Ironfang observed.

Quickstart

Mint an API key with Rig scopes in the Ironfang portal, then commit an ironfang.rig.yaml beside the application. The file declares the external identities a run needs and what the run must observe.

version: 1
project: acme-shop
suite: checkout
name: Checkout flow
defaults:
  run_ttl: 30m
resources:
  customer_email:
    type: email
  stripe_callback:
    type: callback
    connector:
      route: stripe
  shipping_api:
    type: mock_http
    rules:
      - match: { method: POST, path: /v1/ship/* }
        respond: { status: 201, json: { tracking: "IF-1" } }
expectations:
  - id: order_email
    resource: customer_email
    event: email.received
    match: { subject_contains: "Your order" }
  - id: stripe_forwarded
    resource: stripe_callback
    event: connector.request.completed
    match: { outcome: delivered, status: 200 }

The command line client syncs the file, starts a run, exports each resource's address to the command after --, runs it, and finishes the run with a verdict.

export IRONFANG_API_KEY=if_live_...
ironfang-rig run -- npm test

# In the test process:
#   IRONFANG_RIG_RUN_ID           the run
#   IRONFANG_RIG_CUSTOMER_EMAIL   run-....@inbox.rig.ironfang.uk
#   IRONFANG_RIG_STRIPE_CALLBACK  https://hooks.rig.ironfang.uk/h/...
#   IRONFANG_RIG_SHIPPING_API     https://mock.rig.ironfang.uk/m/...

The same run is available over HTTP. Every request carries the key as a bearer token and the base URL is https://api.ironfang.uk/rig.

curl -X POST https://api.ironfang.uk/rig/v1/suites/$SUITE_ID/runs \
  -H "Authorization: Bearer $IRONFANG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "ttl": "30m", "external_id": "ci-4821" }'

The complete OpenAPI 3.1 description is served at https://api.ironfang.uk/rig/openapi.yaml.

Model

Suites persist, runs are disposable, resources have explicit lifetimes. Nothing here runs your code: Ironfang Rig is the outside world your tests talk to and the record of what it saw.

ObjectWhat it is
ProjectA persistent container for one application, named by slug.
SuiteA versioned definition of resources and expectations inside a project. Every revision is kept with its SHA-256; a run always names the version it ran.
RunOne execution of a suite's current version. It is active until it is finished, cancelled or expired by its TTL (default 30 minutes, 1 minute to 24 hours). A finished run has an outcome of pass, fail or none.
ResourceAn external identity the run receives: an inbox, a callback URL, a mock HTTP endpoint or a route to a connector. Unguessable and unique to the run, reachable from the Internet only while the run is active.
EventOne immutable observation on the run's timeline, with a sequence number contiguous from 1. The timeline is append-only and read-only once the run ends.
ExpectationA deterministic condition on the timeline, stored with the suite and judged when the run is finished.
FaultDeterministic interference armed on one resource: delay, duplicate or drop a forwarded callback, or override a mock's status.

Everything is organisation-bound. An object that belongs to another organisation is reported as not found, never as forbidden.

API keys and scopes

Platform API keys are minted in the portal and start with if_live_. A key's scopes are its permissions and it acts only in the organisation it was minted for. Send it as a bearer token.

Authorization: Bearer if_live_...
  • rig:readList and read projects, suites, runs, resources, faults, connectors and events; wait on the timeline; export evidence
  • rig:writeCreate and revise projects and suites
  • rig:runStart, finish and cancel runs; allocate resources; change mock rules; arm faults; replay callbacks
  • rig:connectorMint connectors and their bootstrap tokens
  • rig:*All of the above

A Warden user token is accepted as well, with the acting organisation in X-Ironfang-Tenant; each route then needs the matching tenant permission (rig.read, rig.write, rig.run, rig.connector).

Errors

Every error is a JSON object with a stable code, a message for people and the request id to quote to support.

{
  "error": { "code": "run_not_active", "message": "the run has already ended" },
  "request_id": "..."
}
StatusCodeMeaning
400invalid_json, invalid_queryThe body is not one strict JSON value of the documented shape, or a query parameter is unknown, repeated or malformed.
401unauthorized, invalid_api_keyNo credential, or one that does not resolve.
403forbidden, insufficient_scopeThe key lacks the scope, the user lacks the permission, or the request names another organisation.
404not_foundNo such object in this organisation.
409conflict, archived, run_not_activeA slug is taken, the target is archived, or the run has already ended.
410payload_retiredThe event exists but its payload bytes have passed retention.
422invalid_request, not_replayableA field is invalid; the message names it.
429too_many_waiters, replay_limit, run_limitA per-organisation or per-run bound was reached; the message says which.

The suite file

ironfang.rig.yaml lives in the repository beside the application it tests. It carries exactly the definition the API stores, so what a developer commits is what a run is created from. Syncing the file creates the project and suite on first use and records a new suite version whenever the definition changes.

FieldTypeNotes
versionintegerAlways 1.
projectstringProject slug. Created when first synced.
suitestringSuite slug, unique within the project.
namestringDisplay name, up to 120 characters.
defaults.run_ttldurationDefault run lifetime, 1m to 24h. Defaults to 30m.
resourcesobjectName to resource spec, 1 to 32 entries. Names match ^[a-z][a-z0-9_]{0,63}$ and become environment variable suffixes.
expectationsarrayUp to 64 conditions, each with an id, a resource name, an event type and an optional match.

Resource specs

typeExtra fieldsThe run receives
emailnoneAn inbox address under inbox.rig.ironfang.uk.
callbackconnector.route (optional)A public URL that records every request. With a route, each request is also forwarded to your connector.
mock_httprules (optional, up to 32)A public URL that answers from ordered rules.
connector_routerouteA route label with no public address, for connectors to bind.

Route labels match ^[a-z0-9][a-z0-9-]{0,62}$. The platform only ever names a label; what it points at is decided on the connector's command line and never leaves your machine.

Matching

A match object is a set of conditions on the top-level fields of an event's data, and every key must hold. A plain key compares as JSON, so numbers, booleans, strings and whole objects compare by value. A key ending in _contains requires the field to be a string containing the value. The same rule evaluates suite expectations, the wait endpoint and the CLI's --match, so a condition means one thing everywhere. Up to 16 keys, 4096 characters each.

Resources

Starting a run allocates every declared resource. Each one is unguessable, belongs to this run alone, and stops accepting activity when the run ends. More can be allocated on an active run with POST /v1/runs/{runId}/resources.

Email inboxes

An email resource is an address such as run-k7c3m2wp9e4q5r6t@inbox.rig.ironfang.uk. Mail for it is received by mx.rig.ironfang.uk, parsed, and recorded as email.received with the sender, recipient, subject, links, verification codes, attachments and transport details. The raw message is kept as the event's payload. Messages up to 10 MiB and 500 messages per run are accepted; anything more is refused at SMTP time so the sender sees the bounce.

Callback URLs

A callback resource is a URL such as https://hooks.rig.ironfang.uk/h/.... Any method and any path beneath it are accepted. The request is recorded as callback.received with its method, path, query, content type, byte length and SHA-256, and the sender gets a 200 with the event id at once. The exact bytes are available from the event's payload endpoint. Bodies over 64 KiB are refused with 413 and recorded as callback.rejected; a run accepts up to 1000 callbacks.

{ "received": true, "run_id": "...", "event_id": "...", "sequence": 7 }

With a connector.route, every recorded request is also queued for that route and forwarded through your connector; see Local connector.

Mock HTTP endpoints

A mock_http resource is a base URL such as https://mock.rig.ironfang.uk/m/.... Requests beneath it are answered by the first rule whose match holds; no match answers 404 with code no_mock_rule. Each request is recorded as mock.request.received and its answer as mock.response.sent with the rule that matched. Rules can be replaced while the run is active with PUT /v1/runs/{runId}/resources/{resourceId}/mock, which records mock.rules.updated.

Rule fieldNotes
match.methodAn HTTP method, or * or omitted for any.
match.pathExact path under the mock URL, or a prefix ending in /*; omitted for any.
match.headersHeaders that must be present with exactly these values.
match.jsonTop-level fields a JSON request body must equal.
respond.statusRequired, 100 to 599.
respond.headersResponse headers.
respond.body or respond.jsonA text body up to 64 KiB, or a JSON body that sets the content type unless a header does.
respond.delayHeld before answering, at most 10s.

Deterministic: the same request against the same rules gets the same answer, delay included. A run may make up to 5000 mock calls.

Connector routes

A connector_route has no public address. It exists so a connector can bind the label and so callbacks can name it. Most suites declare the route on the callback itself and never need a separate resource.

Timeline and waiting

GET /v1/runs/{runId}/events?since=0&limit=100 returns the run's events in sequence order after since, up to 500 at a time, with next_since to continue. Sequences are contiguous from 1 and never change, so the same request always returns the same events. Payloads larger than an event carries are referenced by blob_ref, never embedded.

{
  "id": "...",
  "run_id": "...",
  "resource_id": "...",
  "type": "callback.received",
  "sequence": 7,
  "occurred_at": "2026-09-19T09:14:02.118Z",
  "data": {
    "resource": "stripe_callback",
    "method": "POST",
    "path": "/",
    "content_type": "application/json",
    "byte_length": 812,
    "sha256": "..."
  }
}

POST /v1/runs/{runId}/wait blocks until an event after since matches, or the timeout passes. The wait reads the timeline first, so an event that already happened is answered at once, then wakes as new events land.

curl -X POST https://api.ironfang.uk/rig/v1/runs/$RUN_ID/wait \
  -H "Authorization: Bearer $IRONFANG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "email.received",
    "resource": "customer_email",
    "match": { "subject_contains": "Your order" },
    "since": 0,
    "timeout": "30s"
  }'

A timeout is a 200 with matched: false, never an error: nothing went wrong, the thing has not happened yet, and next_since says where to continue. Timeouts run from 1 to 90 seconds (default 30); loop with next_since for longer. Deterministic: the same timeline and request always answer the same event. At most 16 waits per organisation may be open at once.

Payloads

GET /v1/runs/{runId}/events/{eventId}/payload returns the exact bytes a callback or message carried, as an opaque attachment whatever the sender declared, so third-party content is never rendered on the API origin. The declared media type is in X-Ironfang-Payload-Media-Type and the SHA-256 in X-Ironfang-Payload-SHA256. Payload bytes are kept for 7 days after the run ends; after that the event remains and the endpoint answers 410.

Event catalogue

Every type an expectation or a wait can name, with the data fields a match can address. All events also carry resource (the declared name) where one applies.

TypeWhenNotable data
run.startedThe run began.suite_version, ttl_seconds, expires_at, external_id
resource.allocatedOnce per resource.name, type, expires_at
email.receivedA message arrived for an inbox.sender, recipient, subject, from, links, codes, message, transport
callback.receivedA request reached a callback URL.method, path, query, content_type, byte_length, sha256, source_ip, route
callback.rejectedA request was refused, such as a body over the limit.reason, limit_bytes
callback.forwardedA delivery was handed to a connector.delivery_id, event_id, route, connector_id, attempt
connector.request.completedThe connector reported the local result.outcome (delivered or failed), status, headers, body_excerpt, duration_ms, error, delivery_id
callback.replayedA recorded callback was replayed on request.event_id, replay (1, 2, ...), delivery_id, route, requested_by
mock.request.receivedA request reached a mock.method, path, query, content_type, byte_length, matched, rule
mock.response.sentThe mock answered.status, original_status, rule, delay_ms, request_event_id
mock.rules.updatedThe rules were replaced.rules
connector.connected, connector.disconnectedA connector session opened or closed.connector_id, name, routes, reason, requeued_deliveries
fault.added, fault.removed, fault.injectedA fault was armed, disarmed, or fired.fault_id, type, fired, persistent, status, event_id
expectation.passed, expectation.failedJudged at finish, one per expectation.expectation, event
run.completed, run.cancelled, run.expiredThe run ended.outcome, passed, failed, total, reason

Local connector

A callback with a connector.route is forwarded to an application on your machine or CI runner without opening an inbound port. The ironfang-connect binary dials the gateway at wss://connect.rig.ironfang.uk/v1/connect, binds route labels, and makes each forwarded request against the local target you gave for that label. The local result travels back and is recorded as connector.request.completed.

Mint a connector on an active run. The response carries a single-use bootstrap token that expires after ten minutes, the gateway URL and the command line to complete. The token appears here and nowhere else.

curl -X POST https://api.ironfang.uk/rig/v1/runs/$RUN_ID/connectors \
  -H "Authorization: Bearer $IRONFANG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "ci-runner-3", "routes": ["stripe"] }'

# Then, with the token in the environment rather than an argument:
IRONFANG_CONNECT_TOKEN=ift_boot_... ironfang-connect \
  --route stripe=http://127.0.0.1:8080/webhooks/stripe

The connector exchanges the bootstrap token for a session credential bound to this organisation, this run and these routes, which lives no longer than the run. Deliveries for a route wait until a connector holds it, then arrive in order, each with a 30 second local timeout; the result the connector reports is final. A connector that drops mid-run resumes on its session credential, and deliveries it had not answered are requeued for the next session with their attempt count raised. GET /v1/runs/{runId}/connectors reports each connector's state and how many deliveries are still waiting. A run may have up to 16 connectors.

Forwarded requests carry the original method, path, query, headers and body, plus X-Ironfang-Delivery-ID, X-Ironfang-Event-ID and X-Ironfang-Route, so the application can tell a replay or duplicate from a first delivery. Frames are bounded at 256 KiB.

Faults

A fault is deterministic interference armed on one resource of an active run with POST /v1/runs/{runId}/faults. It fires on the next observation it applies to, count times (default once) or, if persistent, until removed or the run ends. Every firing is a fault.injected event beside the observation it acted on. No randomness: the same faults against the same traffic give the same timeline.

curl -X POST https://api.ironfang.uk/rig/v1/runs/$RUN_ID/faults \
  -H "Authorization: Bearer $IRONFANG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "type": "duplicate", "resource": "stripe_callback", "copies": 2 }'
typeActs onEffect
delayA callback with a connector routeThe delivery is held for delay, at most 30s, before it is forwarded.
duplicateA callback with a connector routeThe delivery is forwarded copies extra times (1 to 5, default 1), each with its own delivery id.
dropA callback with a connector routeThe callback is recorded but never forwarded.
status_overrideA mock HTTP endpointThe mock answers status whatever its rules said; body and headers are unchanged and the original status is recorded.

One active fault of each type per resource, and up to 64 faults per run. When several apply to the same delivery, drop wins, then delay, then duplicate. DELETE /v1/runs/{runId}/faults/{faultId} disarms one and records fault.removed; the row is kept, because the fault history is evidence. Faults never act on a replay, which is the test's own explicit act.

Expectations and verdicts

Expectations are judged when the run is finished, against the whole timeline. Each one passes if any event of its type on its resource satisfies its match, and the first such event is recorded with the verdict. The run's outcome is derived from the verdicts: pass when every expectation passed, fail when any failed, none for a suite without expectations. Pass an outcome to override the derivation when your own test harness knows better.

curl -X POST https://api.ironfang.uk/rig/v1/runs/$RUN_ID/finish \
  -H "Authorization: Bearer $IRONFANG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "npm test exited 0" }'
{
  "run": { "id": "...", "status": "finished", "outcome": "pass", ... },
  "expectations": [
    { "id": "order_email", "resource": "customer_email", "event": "email.received",
      "passed": true, "event_id": "...", "sequence": 9 },
    { "id": "stripe_forwarded", "resource": "stripe_callback",
      "event": "connector.request.completed", "passed": true, "event_id": "...", "sequence": 12 }
  ]
}

Finishing records one expectation.passed or expectation.failed per expectation and then run.completed. A run that is cancelled or reaches its TTL ends with run.cancelled or run.expired and no verdicts. Send an Idempotency-Key header when starting a run so a retried CI step returns the run it already started.

Replay

POST /v1/runs/{runId}/events/{eventId}/replay forwards a callback the run received to its connector route again, exactly as recorded, to prove the application handles a repeat. The event must be a callback.received on a resource with a connector route, and the run must be active.

curl -X POST https://api.ironfang.uk/rig/v1/runs/$RUN_ID/events/$EVENT_ID/replay \
  -H "Authorization: Bearer $IRONFANG_API_KEY"

{ "event": { "type": "callback.replayed", "data": { "event_id": "...", "replay": 1, ... } },
  "delivery_id": "..." }

The replay is recorded as callback.replayed and then travels like any delivery: the timeline shows its own callback.forwarded and connector.request.completed against the original event id, and the completion names the delivery_id the response gave you. Armed faults do not act on a replay. At most 200 replays per run; a 422 not_replayable names an event that is not a forwardable callback.

Evidence bundle

POST /v1/runs/{runId}/evidence builds the run's evidence bundle from what it recorded and streams it as a ZIP. It is read-only: the bundle is what the caller can already read event by event, packaged so it can be attached to a ticket, an audit or a release.

FileContents
manifest.jsonThe run, counts, and every file's size and SHA-256. Written last, so a complete manifest means a complete bundle.
events.jsonThe whole timeline in sequence order.
expectations.jsonEach expectation with its verdict once the run is finished.
faults.json, resources.json, definition.jsonThe faults with how often each fired, the resources with their addresses, and the suite version the run executed.
messages/, requests/Every received message and every callback body, named by sequence and event id.

Bounded at 10,000 events and 256 MiB of payloads; the manifest says when a bound bit, and lists payloads that retention had already taken. With Accept: application/json the manifest alone is returned, with the path that downloads the bundle.

ironfang-rig evidence $RUN_ID -o checkout-$RUN_ID.zip
# prints the bundle's SHA-256

Command line

ironfang-rig is a single static binary for Linux, macOS and Windows, published with a SHA-256 checksums file per release at the releases page. It reads the key from IRONFANG_API_KEY or --api-key-file, never from an argument, and talks to https://api.ironfang.uk/rig unless IRONFANG_API_URL says otherwise.

ironfang-rig sync     [-f ironfang.rig.yaml]
ironfang-rig run      [-f file] [--ttl 30m] [--external-id id] [--output text|env|github|json] [-- command args...]
ironfang-rig finish   <run-id> [--outcome pass|fail|none] [--reason text]
ironfang-rig status   <run-id>
ironfang-rig events   <run-id> [--since 0] [--json]
ironfang-rig wait     <run-id> --type <event type> [--resource name] [--match key=value]... [--since 0] [--timeout 30s]
ironfang-rig evidence <run-id> [-o ironfang-rig-<run-id>.zip]
ironfang-rig replay   <run-id> <event-id>
ironfang-rig version

run syncs the suite, starts a run and prints its addresses. With a command after --, it runs that command with IRONFANG_RIG_RUN_ID and one IRONFANG_RIG_<RESOURCE> variable per resource, then finishes the run. --output env prints those assignments for a shell to source; --output github writes them to the job's outputs and environment.

Exit codeMeaning
0Success; for run and finish, the run's outcome is not fail.
1An error talking to the API or the file system.
2Usage: a missing argument or an invalid flag.
3The command after -- failed, or the run's outcome is fail.
4wait saw no matching event before its timeout.

GitHub Actions

Two composite actions in the public ironfang-ltd/rig-action repository wrap the CLI. start syncs the suite file, starts a run and exports its addresses as step outputs and job environment variables; finish finishes the run, prints the verdicts and fails the step when the outcome is fail. Store the key as a repository secret.

jobs:
  integration:
    runs-on: ubuntu-latest
    env:
      IRONFANG_API_KEY: ${{ secrets.IRONFANG_API_KEY }}
    steps:
      - uses: actions/checkout@v4
      - id: test
        uses: ironfang-ltd/rig-action/start@v1
        with:
          suite-file: ironfang.rig.yaml
          ttl: 20m
      - run: npm test
        # IRONFANG_RIG_RUN_ID and IRONFANG_RIG_<RESOURCE> are in the environment
      - if: always()
        uses: ironfang-ltd/rig-action/finish@v1
        with:
          run-id: ${{ steps.test.outputs.run_id }}
InputActionNotes
suite-filestartPath to the suite file. Default ironfang.rig.yaml.
ttlstartRun lifetime, 1m to 24h. Default from the suite.
external-idstartYour reference for the run. Default the workflow run id.
run-idfinishRequired. The run_id output of the start step.
outcomefinishpass, fail or none to override the verdicts. Default derived.
fail-onfinishfail (default) or never.
version, binarybothThe ironfang-rig release to download and verify against its checksums (default: the release the action was cut with), or a prebuilt binary path.

The start step also exposes resources, a JSON object of resource name to address, for steps that prefer to read it as data.

MCP tools

The Ironfang MCP server exposes the same product to coding assistants, so an agent can start a run, read the timeline, arm a fault and export the evidence without leaving the editor. Every tool is organisation-bound to the connection and costs no credits. Tools that create or change something say so in their metadata.

ToolScopeDoes
rig.project.list, rig.project.createrig:read, rig:writeList projects; create one by slug.
rig.suite.list, rig.suite.get, rig.suite.upsertrig:read, rig:writeRead suites and their current definition; create or revise one from a definition.
rig.run.create, rig.run.get, rig.run.finish, rig.run.cancelrig:run, rig:readStart a run and receive its addresses; read it; finish it for verdicts; cancel it.
rig.resource.createrig:runAllocate one more resource on an active run.
rig.event.list, rig.event.waitrig:readPage the timeline; block for a matching event.
rig.event.replayrig:runReplay a recorded callback to its route.
rig.fault.add, rig.fault.removerig:runArm and disarm faults.
rig.connector.prepare, rig.connector.statusrig:connector, rig:readMint a connector and receive the command to run locally; see which connectors are online and what is waiting.
rig.evidence.exportrig:readThe evidence manifest and where to download the bundle.

Payload bytes are never returned through the connection; the tools return what the timeline recorded and point at the API for the rest.

Limits and retention

BoundValue
Run lifetime1 minute to 24 hours; default 30 minutes
Resources per suite32
Expectations per suite64, up to 16 match keys each
Messages per inbox run500, each up to 10 MiB
Callbacks per run1000, each body up to 64 KiB
Mock calls per run5000; 32 rules, 64 KiB body and 10 second delay per rule
Faults per run64; delay up to 30 seconds; up to 5 duplicate copies; count up to 100
Connectors per run16; 30 second local timeout per delivery; 256 KiB frames
Replays per run200
Open waits per organisation16; timeouts 1 to 90 seconds
Events per page500
Evidence bundle10,000 events and 256 MiB of payloads
Payload retention7 days after the run ends
Run retention90 days after the run ends, then the run and its timeline are deleted

Export the evidence bundle before retention runs if a run must outlive it. Suites, their versions and projects persist.

API reference

Base URL https://api.ironfang.uk/rig. The OpenAPI 3.1 document at https://api.ironfang.uk/rig/openapi.yaml carries every schema, example and error, with stable operation ids for generated clients. Ids are UUIDs; lists page with cursor and limit (up to 100).

EndpointScopeDoes
GET /v1/projectsrig:readList projects.
POST /v1/projectsrig:writeCreate a project by slug.
GET /v1/projects/{projectId}rig:readGet a project.
GET /v1/suitesrig:readList suites, optionally by project.
POST /v1/suitesrig:writeCreate a suite with its first version.
GET /v1/suites/{suiteId}rig:readGet a suite with its current version.
PUT /v1/suites/{suiteId}rig:writeRevise the definition; an unchanged definition records no new version.
GET /v1/suites/{suiteId}/versionsrig:readList a suite's versions.
POST /v1/suites/{suiteId}/runsrig:runStart a run and allocate its resources. Honours Idempotency-Key.
GET /v1/runsrig:readList runs, newest first.
GET /v1/runs/{runId}rig:readGet a run.
POST /v1/runs/{runId}/finishrig:runFinish the run and judge its expectations.
POST /v1/runs/{runId}/cancelrig:runCancel the run without verdicts.
GET /v1/runs/{runId}/resourcesrig:readList the run's resources with their addresses.
POST /v1/runs/{runId}/resourcesrig:runAllocate one more resource.
PUT /v1/runs/{runId}/resources/{resourceId}/mockrig:runReplace a mock's rules.
GET /v1/runs/{runId}/faultsrig:readList the run's faults and how often each fired.
POST /v1/runs/{runId}/faultsrig:runArm a fault.
DELETE /v1/runs/{runId}/faults/{faultId}rig:runDisarm a fault.
GET /v1/runs/{runId}/connectorsrig:readList connectors and pending deliveries.
POST /v1/runs/{runId}/connectorsrig:connectorMint a connector and its bootstrap token.
GET /v1/runs/{runId}/eventsrig:readRead the timeline from since.
POST /v1/runs/{runId}/waitrig:readWait for a matching event.
GET /v1/runs/{runId}/events/{eventId}rig:readGet one event.
GET /v1/runs/{runId}/events/{eventId}/payloadrig:readDownload an event's payload bytes.
POST /v1/runs/{runId}/events/{eventId}/replayrig:runReplay a recorded callback.
POST /v1/runs/{runId}/evidencerig:readExport the evidence bundle or its manifest.
GET /v1/usagerig:readRuns and events in a window, month to date by default.
GET /v1/environmentrig:readHosts, gateway, connector release, every bound and the retention windows.
GET /v1/homerig:readCounts, setup moments and recent runs, as the portal shows them.