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   [email protected]
#   IRONFANG_RIG_STRIPE_CALLBACK  https://hooks.rig.ironfang.com/h/...
#   IRONFANG_RIG_SHIPPING_API     https://mock.rig.ironfang.com/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.com/rig.

curl -X POST https://api.ironfang.com/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.com/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, drop, reorder or change the body of a forwarded callback; override a mock's status, reset its connection, throttle its body or cut it short.

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",
    "docs": "https://ironfang.com/rig/docs#errors"
  },
  "request_id": "01a0c375-7bae-7f02-a3d4-91e6b8c25f70"
}

docs is a link to this section, and request_id repeats the X-Ironfang-Request-ID response header.

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, connector_limit, fault_limitA per-organisation or per-run bound was reached; the message says which. A run's callback, mock and mail addresses answer run_limit the same way once the run has received its limit.

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.com.
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 [email protected]. Mail for it is received by mx.rig.ironfang.com, 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.

Every message is also checked as a receiving mail server would: SPF for the sending address against the envelope sender's domain, DKIM for each signature it carries, and DMARC for the From domain with the alignment and policy its record asks for. The results are recorded on the event as authentication (spf, dkim[], dmarc with the domain, policy, alignment and reasons) and lifted as the one-word fields spf, dkim and dmarc, so an expectation or a wait can ask for { "dmarc": "pass" }. They are observations of what the sender published at the moment of receipt: a DNS failure is temperror, never a verdict.

Callback URLs

A callback resource is a URL such as https://hooks.rig.ironfang.com/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.com/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, a prefix ending in /*, or a template such as /v1/pets/{id} whose braced segments match any one segment; 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.
respond.templateRender the body and header values from the request: {{request.method}}, {{request.path}}, {{request.query.name}}, {{request.header.Name}}, {{request.body}}, {{request.json.a.b.0}}, {{run.id}}, {{resource.name}}, {{step}}, {{received_at}}. Inside a JSON body a placeholder sits in a string and is escaped for it; an unknown name is refused when the rules are written.
sequence, repeatInstead of respond: 2 to 16 responses the rule gives its calls in turn, then the last again or, with repeat: cycle, from the first. The response event records the step; replacing the rules starts every sequence afresh.

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

A mock can stand in for a documented API instead of carrying rules: give the resource openapi.file (a path beside the suite file, which the CLI inlines) or openapi.document (the OpenAPI 3 text, JSON or YAML, up to 256 KiB). One rule is derived per operation, in document order: the path template is the match, the lowest 2xx or default response the answer, and the documented example, or a value built from the schema (enum first, format-shaped strings, zero numbers, one array element), the body. References resolve within the document and nothing is fetched. A mock holds at most 32 operations; name the ones a suite needs in openapi.include as GET /pets/{id}, and set openapi.prefer: schema to ignore the examples.

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.com/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 rig connect binary dials the gateway at wss://connect.rig.ironfang.com/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.com/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 rig 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.com/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.
reorderA callback with a connector routeDeliveries are held until batch of them (2 to 10) are waiting, then released last-first, so the connector receives them in reverse. The fault fires once per batch and names the order; a batch still incomplete when the run ends expires with outcome held.
payload_mutationA callback with a connector routeThe delivery carries a body changed by mutations, in order: set or remove at a JSON Pointer, replace text, or truncate to a length. The received body stays on the timeline untouched; the body sent is the payload of the fault.injected event, which also says which changes applied.
connection_resetA mock HTTP endpointThe request is recorded, then the connection is closed without an answer. Directly, the caller sees a reset; through the public edge it sees the proxy's bad-gateway answer. Either way there is no valid response.
bandwidth_limitA mock HTTP endpointThe body is sent at bytes_per_second (64 to 1048576), flushed in tenth-of-a-second chunks. A body that would take longer than 30 seconds is sent at the rate that fits, and the event says it was capped.
partial_responseA mock HTTP endpointThe headers, with the full content length, and the first bytes of the body are sent, then the connection closes: the caller sees a body cut short.

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; a reorder holds the delivery regardless of its delay, and a payload mutation shapes every copy. The mock faults act together in the order they were armed: a status override changes the status, and a reset, a throttle or a cut applies to what is then sent. 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.com/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.com/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.
signature.jsonAn Ed25519 signature over manifest.json exactly as written, with the key id and the public key, when the platform has a signing key; the manifest says signing either way.

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.

Proof of action

Every event carries a hash: SHA-256 over the hash of the event before it, the run id, the sequence, the type, the timestamp and the event's data in canonical JSON (keys in byte order, no whitespace, numbers as written), joined by newlines. The run carries the chain's head as chain_head, the manifest records the chain it recomputed while building the bundle, and a changed byte anywhere breaks the chain at that event. Events from before hashing began are an unhashed prefix, which the report names.

POST /v1/runs/{runId}/receipt is a signed statement of the run: project, suite and version, status and outcome, every verdict, the event count and the chain head, signed over its canonical JSON. GET /v1/evidence/keys publishes the signing keys by id. Both the bundle and the receipt carry the key they were signed with, so they verify anywhere:

ironfang rig evidence verify ironfang-rig-$RUN_ID.zip --key <base64 public key>
ironfang rig receipt $RUN_ID --key <base64 public key>

The verify command checks every file's digest against the manifest, recomputes the chain from events.json, and checks the signature; with --key the signature must be by a key you trust, without it the key the bundle carries is used and the report says so. Exit code 3 means a check failed.

Retention takes payloads after 7 days and runs after 90. PUT /v1/runs/{runId}/hold with { "until": "..." } (at most a year ahead) keeps a run and everything it recorded until then; DELETE lifts the hold. Both are events on the run's own timeline, retention.held and retention.released.

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.com/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.export, rig.evidence.receiptrig:readThe evidence manifest and where to download the bundle, and a signed receipt of what the run did.

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.com/rig. The OpenAPI 3.1 document at https://api.ironfang.com/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.

Machine interfaces

InterfaceDetails
Product pagehttps://ironfang.com/rig
Documentationhttps://ironfang.com/rig/docs
API base URLhttps://api.ironfang.com/rig
OpenAPI contracthttps://api.ironfang.com/rig/openapi.yaml. The same contract is served as JSON at https://api.ironfang.com/rig/openapi.json.
AuthenticationPlatform API key as a bearer token
Errorshttps://ironfang.com/rig/docs#errors. A JSON body with a stable code, a message, this link and the request id.
MCPAvailable. Projects and suites, runs and their resources, the timeline and waits, replay, deterministic faults, the local connector bootstrap, evidence manifests and signed receipts. Tools are named rig.*. MCP server reference; every tool and schema without a token at /.well-known/ironfang-mcp.json
Discovery/apis.json, /.well-known/api-catalog and /llms.txt