{"components":{"parameters":{"Cursor":{"description":"The next_cursor of the previous page.","in":"query","name":"cursor","required":false,"schema":{"type":"string"}},"EventId":{"in":"path","name":"eventId","required":true,"schema":{"format":"uuid","type":"string"}},"FaultId":{"in":"path","name":"faultId","required":true,"schema":{"format":"uuid","type":"string"}},"IdempotencyKey":{"description":"Makes starting a run safe to retry. Any string up to 200 characters; surrounding whitespace is ignored and a longer key is a 422 `invalid_request`. The same key in this organisation returns the run it first started, with its resources and 200 rather than 201, for as long as that run is kept. The request is not compared with the first, so there is no conflict response.","in":"header","name":"Idempotency-Key","required":false,"schema":{"maxLength":200,"type":"string"}},"Limit":{"in":"query","name":"limit","required":false,"schema":{"default":50,"maximum":100,"minimum":1,"type":"integer"}},"ProjectId":{"in":"path","name":"projectId","required":true,"schema":{"format":"uuid","type":"string"}},"ResourceId":{"in":"path","name":"resourceId","required":true,"schema":{"format":"uuid","type":"string"}},"RunId":{"in":"path","name":"runId","required":true,"schema":{"format":"uuid","type":"string"}},"SuiteId":{"in":"path","name":"suiteId","required":true,"schema":{"format":"uuid","type":"string"}}},"responses":{"Conflict":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"A name is already taken, or the target is archived (`conflict`, `archived`)."},"Forbidden":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The credential lacks the permission or scope, or names another organisation (`forbidden`, `insufficient_scope`)."},"InvalidJSON":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The body is not one strict JSON value of the documented shape (`invalid_json`)."},"InvalidQuery":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"An unknown, repeated or malformed query parameter (`invalid_query`)."},"InvalidRequest":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"A field is invalid; the message names it (`invalid_request`)."},"NotFound":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"No such object in this organisation (`not_found`)."},"RunNotActive":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The run has already ended (`run_not_active`)."},"Unauthorized":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"No credential, an invalid one, or a delegated token where none is accepted (`unauthorized`, `invalid_api_key`, `delegated_not_allowed`)."}},"schemas":{"ChainReport":{"description":"The event hash chain as recomputed over a timeline.","properties":{"broken_at":{"description":"The first sequence whose hash did not recompute.","format":"int64","type":"integer"},"events":{"type":"integer"},"hashed":{"type":"integer"},"hashed_from":{"description":"The sequence the chain starts at.","format":"int64","type":"integer"},"head":{"type":"string"},"valid":{"type":"boolean"}},"required":["events","hashed","valid"],"type":"object"},"Connector":{"description":"One connector to a run. Its credentials never appear here: the bootstrap token is returned once at creation, the session credential once at the gateway.","properties":{"bootstrap_expires_at":{"format":"date-time","type":"string"},"bootstrap_used_at":{"format":"date-time","type":"string"},"client":{"additionalProperties":true,"description":"What the connector said about itself at hello (version, os, arch).","type":"object"},"connected_at":{"format":"date-time","type":"string"},"created_at":{"format":"date-time","type":"string"},"created_by":{"type":"string"},"disconnected_at":{"format":"date-time","type":"string"},"id":{"format":"uuid","type":"string"},"last_seen_at":{"format":"date-time","type":"string"},"name":{"type":"string"},"organisation_id":{"format":"uuid","type":"string"},"routes":{"description":"The route labels the credential is bound to.","items":{"type":"string"},"type":"array"},"run_id":{"format":"uuid","type":"string"},"session_expires_at":{"description":"Never later than the run's expiry.","format":"date-time","type":"string"},"status":{"enum":["pending","online","offline"],"type":"string"}},"required":["id","organisation_id","run_id","routes","status","bootstrap_expires_at","created_at"],"type":"object"},"ConnectorSpec":{"additionalProperties":false,"description":"Binds a callback to a local route by label. The label is all the platform ever sends; the destination behind it belongs to the connector on the developer's machine.","properties":{"route":{"example":"stripe","pattern":"^[a-z0-9][a-z0-9-]{0,62}$","type":"string"}},"required":["route"],"type":"object"},"Definition":{"additionalProperties":false,"description":"A suite's content. Resources are named identities a run receives; expectations are deterministic conditions on the run's events, stored and validated now and evaluated once the platform observes the events they name.","properties":{"defaults":{"additionalProperties":false,"properties":{"run_ttl":{"description":"Default run lifetime, 1m to 24h (\"30m\").","example":"30m","type":"string"}},"type":"object"},"expectations":{"items":{"$ref":"#/components/schemas/ExpectationSpec"},"maxItems":64,"type":"array"},"resources":{"additionalProperties":{"$ref":"#/components/schemas/ResourceSpec"},"example":{"customer_email":{"type":"email"},"shipping_api":{"type":"mock_http"},"stripe_callback":{"connector":{"route":"stripe"},"type":"callback"}},"maxProperties":32,"minProperties":1,"type":"object"},"version":{"const":1,"type":"integer"}},"required":["version","resources"],"type":"object"},"Environment":{"properties":{"connect_url":{"example":"wss://connect.rig.ironfang.com/v1/connect","format":"uri","type":"string"},"hooks_base_url":{"example":"https://hooks.rig.ironfang.com","format":"uri","type":"string"},"inbox_domain":{"example":"inbox.rig.ironfang.com","type":"string"},"limits":{"additionalProperties":{"type":"integer"},"description":"Every capacity bound, in the unit its name says. These are the same whatever the account pays; billing refusals are separate (see metering).","type":"object"},"metering":{"description":"What is metered where unified billing runs. Each counted event is one rig.interaction, once per transport identity. An interaction the billing ledger refuses is not recorded: the sender gets the billing code (HTTP callbacks and mocks the problem's status, such as 429 free_allowance_exhausted, in the usual error body with product, meter and reset_at beside the code; mail a 452 4.2.2 reply naming the code, or 550 for a closed account) and the run's timeline gets refusal_event.","properties":{"counted_events":{"example":["email.received","callback.received","mock.request.received"],"items":{"type":"string"},"type":"array"},"enabled":{"description":"Whether this deployment meters interactions.","type":"boolean"},"meter":{"const":"rig.interaction","type":"string"},"refusal_event":{"const":"usage.refused","type":"string"}},"required":["enabled","meter","counted_events","refusal_event"],"type":"object"},"mock_base_url":{"example":"https://mock.rig.ironfang.com","format":"uri","type":"string"},"release":{"description":"The ironfang-connect build to run, when the API knows it.","nullable":true,"properties":{"checksums_url":{"format":"uri","type":"string"},"version":{"type":"string"}},"required":["version","checksums_url"],"type":"object"},"retention":{"properties":{"payload_days":{"description":"Days after a run ends that raw message and request bytes are kept.","type":"integer"},"run_days":{"description":"Days after a run ends that the run and its timeline are kept.","type":"integer"}},"required":["payload_days","run_days"],"type":"object"}},"required":["inbox_domain","hooks_base_url","mock_base_url","connect_url","limits","metering","retention"],"type":"object"},"Error":{"properties":{"error":{"properties":{"code":{"type":"string"},"docs":{"description":"Where the API and its error codes are documented.","format":"uri","type":"string"},"message":{"type":"string"}},"required":["code","message"],"type":"object"},"request_id":{"description":"The X-Ironfang-Request-ID of the failed request.","type":"string"}},"required":["error"],"type":"object"},"Event":{"properties":{"blob_ref":{"description":"Reference to a stored payload too large to embed.","type":"string"},"data":{"additionalProperties":true,"type":"object"},"id":{"format":"uuid","type":"string"},"occurred_at":{"description":"Ironfang's clock.","format":"date-time","type":"string"},"organisation_id":{"format":"uuid","type":"string"},"project_id":{"format":"uuid","type":"string"},"resource_id":{"format":"uuid","type":"string"},"run_id":{"format":"uuid","type":"string"},"sequence":{"description":"The run's own counter, contiguous from 1.","type":"integer"},"suite_id":{"format":"uuid","type":"string"},"type":{"example":"resource.allocated","type":"string"}},"required":["id","organisation_id","project_id","suite_id","run_id","type","sequence","occurred_at","data"],"type":"object"},"ExpectationResult":{"properties":{"event":{"type":"string"},"event_id":{"description":"The first event that satisfied the expectation.","format":"uuid","type":"string"},"id":{"type":"string"},"passed":{"type":"boolean"},"reason":{"description":"Why it failed.","type":"string"},"resource":{"type":"string"},"sequence":{"type":"integer"}},"required":["id","resource","event","passed"],"type":"object"},"ExpectationSpec":{"additionalProperties":false,"properties":{"event":{"description":"An event type from the catalogue, such as email.received or callback.forwarded.","type":"string"},"id":{"example":"verification_email","pattern":"^[a-z][a-z0-9_]{0,63}$","type":"string"},"match":{"additionalProperties":true,"example":{"subject_contains":"Verify"},"maxProperties":16,"type":"object"},"resource":{"description":"A declared resource name.","type":"string"}},"required":["id","resource","event"],"type":"object"},"Fault":{"properties":{"created_at":{"format":"date-time","type":"string"},"created_by":{"type":"string"},"fired":{"type":"integer"},"id":{"format":"uuid","type":"string"},"organisation_id":{"format":"uuid","type":"string"},"remaining":{"description":"Firings left; absent when persistent.","type":"integer"},"removed_at":{"format":"date-time","type":"string"},"resource":{"type":"string"},"resource_id":{"format":"uuid","type":"string"},"run_id":{"format":"uuid","type":"string"},"spec":{"$ref":"#/components/schemas/FaultSpec"},"status":{"enum":["active","spent","removed"],"type":"string"},"type":{"enum":["delay","duplicate","drop","status_override","reorder","payload_mutation","connection_reset","bandwidth_limit","partial_response"],"type":"string"}},"required":["id","organisation_id","run_id","resource_id","resource","type","spec","status","fired","created_at"],"type":"object"},"FaultSpec":{"additionalProperties":false,"description":"Deterministic interference on one resource. delay, duplicate, drop, reorder and payload_mutation act on the forwarding of a callback with a connector route; status_override, connection_reset, bandwidth_limit and partial_response act on a mock's answer. Each type takes its own parameter and no other.","properties":{"batch":{"description":"For reorder: how many deliveries to hold before releasing them last-first. A batch left incomplete when the run ends expires with outcome held.","maximum":10,"minimum":2,"type":"integer"},"bytes":{"description":"For partial_response: how much of the body the mock sends before closing the connection; 0 sends the headers alone.","maximum":65536,"minimum":0,"type":"integer"},"bytes_per_second":{"description":"For bandwidth_limit: the rate the mock sends its body at. Raised, and recorded as capped, if the body would otherwise take longer than 30s.","maximum":1048576,"minimum":64,"type":"integer"},"copies":{"description":"For duplicate: extra deliveries; default 1.","maximum":5,"minimum":1,"type":"integer"},"count":{"description":"How many times the fault fires before it is spent; default 1. A reorder fires once per released batch.","maximum":100,"minimum":1,"type":"integer"},"delay":{"description":"For delay: a duration such as \"500ms\" or \"5s\", at most 30s.","type":"string"},"mutations":{"description":"For payload_mutation: the changes made to the forwarded body, in order. The received body stays on the timeline untouched; the body sent is the payload of the fault.injected event.","items":{"$ref":"#/components/schemas/Mutation"},"maxItems":8,"minItems":1,"type":"array"},"persistent":{"description":"Fire on every observation until removed; count is then not given.","type":"boolean"},"resource":{"description":"The resource, by the name the suite gave it.","type":"string"},"status":{"description":"For status_override: the status to answer.","maximum":599,"minimum":100,"type":"integer"},"type":{"enum":["delay","duplicate","drop","status_override","reorder","payload_mutation","connection_reset","bandwidth_limit","partial_response"],"type":"string"}},"required":["type","resource"],"type":"object"},"MockResponse":{"additionalProperties":false,"description":"One answer. With template, the body and the header values are rendered from the request: {{request.method}}, {{request.path}}, {{request.query}}, {{request.query.name}}, {{request.header.Name}}, {{request.body}}, {{request.json.a.b.0}}, {{run.id}}, {{resource.name}}, {{step}}, {{call}} and {{received_at}}. In a json body a placeholder sits inside a string and is escaped for it. An unknown placeholder is refused when the rules are written.","properties":{"body":{"description":"A text body; exclusive with json.","maxLength":65536,"type":"string"},"delay":{"description":"Held before answering, a duration such as \"250ms\", at most 10s.","type":"string"},"headers":{"additionalProperties":{"type":"string"},"type":"object"},"json":{"description":"A JSON body; sets Content-Type unless a header does."},"status":{"example":201,"maximum":599,"minimum":100,"type":"integer"},"template":{"description":"Render the body and header values from the request.","type":"boolean"}},"required":["status"],"type":"object"},"MockRule":{"additionalProperties":false,"description":"One rule of a mock: what to match and what to answer. Rules are evaluated in order and the first match answers. A rule answers with respond, one answer, or with sequence, its calls taking the responses in turn and repeat saying what later calls get. Deterministic: the same request against the same rules, at the same step, gets the same answer, delay included.","properties":{"match":{"additionalProperties":false,"properties":{"headers":{"additionalProperties":{"type":"string"},"description":"Headers that must be present with exactly these values.","type":"object"},"json":{"additionalProperties":true,"description":"Top-level fields a JSON request body must equal.","type":"object"},"method":{"description":"An HTTP method, or \"*\" or omitted for any.","example":"POST","type":"string"},"path":{"description":"Exact path under the mock URL, a prefix ending in \"/*\", or a template whose {name} segments match any one segment; omitted for any.","example":"/v1/ship/{id}","type":"string"}},"type":"object"},"repeat":{"description":"After a sequence's last response: keep giving the last (the default) or start again.","enum":["last","cycle"],"type":"string"},"respond":{"$ref":"#/components/schemas/MockResponse"},"sequence":{"description":"The responses this rule gives its calls in turn; exclusive with respond. Replacing the rules starts every sequence afresh.","items":{"$ref":"#/components/schemas/MockResponse"},"maxItems":16,"minItems":2,"type":"array"}},"required":["match"],"type":"object"},"Mutation":{"additionalProperties":false,"description":"One deterministic change to a forwarded body. set and remove address a JSON body by JSON Pointer (RFC 6901) and re-encode it with sorted keys; replace substitutes every occurrence of a string in any body; truncate keeps the first bytes. A change that does not apply (a body that is not JSON, a path that does not exist, text not found, a body already shorter) is recorded as not applied and the rest still run.","properties":{"bytes":{"description":"For truncate: how many bytes to keep.","maximum":65536,"minimum":0,"type":"integer"},"find":{"description":"For replace: the text to find.","maxLength":1024,"type":"string"},"op":{"enum":["set","remove","replace","truncate"],"type":"string"},"path":{"description":"For set and remove: a JSON Pointer such as \"/customer/email\" or \"/items/0\"; \"\" names the whole body, \"-\" appends to an array.","maxLength":256,"type":"string"},"replace":{"description":"For replace: the text put in its place; may be empty.","maxLength":1024,"type":"string"},"value":{"description":"For set: any JSON value, at most 4096 bytes encoded."}},"required":["op"],"type":"object"},"OpenAPISpec":{"additionalProperties":false,"description":"For a mock_http, an OpenAPI 3 document the mock stands in for, instead of rules: one rule per operation, in document order, its path template as the match, its lowest 2xx (or default) response as the answer, and the documented example, or a value built from the schema, as the body. References resolve within the document; nothing is fetched. At most 32 operations; name the ones a suite needs in include. The CLI inlines openapi.file from beside the suite file; the platform takes document only.","properties":{"document":{"description":"The OpenAPI 3 document, JSON or YAML.","maxLength":262144,"type":"string"},"include":{"description":"Operations to keep, as \"GET /pets/{id}\".","items":{"type":"string"},"maxItems":32,"type":"array"},"prefer":{"description":"Whether a documented example (the default) or a value built from the schema answers.","enum":["example","schema"],"type":"string"}},"required":["document"],"type":"object"},"Project":{"properties":{"archived_at":{"format":"date-time","type":"string"},"created_at":{"format":"date-time","type":"string"},"id":{"format":"uuid","type":"string"},"name":{"type":"string"},"organisation_id":{"format":"uuid","type":"string"},"slug":{"type":"string"}},"required":["id","organisation_id","slug","name","created_at"],"type":"object"},"PublicKey":{"properties":{"algorithm":{"enum":["ed25519"],"type":"string"},"key_id":{"description":"The first 16 hex characters of the key's SHA-256.","type":"string"},"public_key":{"description":"Base64.","type":"string"}},"required":["key_id","algorithm","public_key"],"type":"object"},"Receipt":{"properties":{"chain_head":{"type":"string"},"events":{"type":"integer"},"generated_at":{"format":"date-time","type":"string"},"hashed_from":{"format":"int64","type":"integer"},"receipt_version":{"type":"integer"},"run":{"properties":{"external_id":{"type":"string"},"finished_at":{"format":"date-time","type":"string"},"id":{"format":"uuid","type":"string"},"organisation_id":{"format":"uuid","type":"string"},"outcome":{"type":"string"},"project":{"type":"string"},"started_at":{"format":"date-time","type":"string"},"status":{"type":"string"},"suite":{"type":"string"},"suite_version":{"type":"integer"}},"required":["id","organisation_id","project","suite","suite_version","status","outcome","started_at"],"type":"object"},"verdicts":{"additionalProperties":{"enum":["passed","failed","unjudged"],"type":"string"},"type":"object"}},"required":["receipt_version","generated_at","run","events"],"type":"object"},"Resource":{"properties":{"config":{"additionalProperties":true,"type":"object"},"created_at":{"format":"date-time","type":"string"},"email_address":{"description":"For email resources - the inbox that receives for this run.","type":"string"},"expires_at":{"format":"date-time","type":"string"},"id":{"format":"uuid","type":"string"},"name":{"type":"string"},"organisation_id":{"format":"uuid","type":"string"},"public_url":{"description":"For callback and mock resources - the unguessable URL to configure elsewhere.","format":"uri","type":"string"},"route_label":{"description":"For connector routes, and callbacks forwarded through one.","type":"string"},"run_id":{"format":"uuid","type":"string"},"scope":{"enum":["run"],"type":"string"},"type":{"enum":["email","callback","mock_http","connector_route"],"type":"string"}},"required":["id","organisation_id","run_id","name","type","scope","expires_at","created_at"],"type":"object"},"ResourceSpec":{"additionalProperties":false,"properties":{"connector":{"$ref":"#/components/schemas/ConnectorSpec"},"openapi":{"$ref":"#/components/schemas/OpenAPISpec"},"route":{"description":"The route label of a connector_route.","type":"string"},"rules":{"description":"For a mock_http, the rules it answers with, in order.","items":{"$ref":"#/components/schemas/MockRule"},"maxItems":32,"type":"array"},"scope":{"default":"run","enum":["run"],"type":"string"},"type":{"enum":["email","callback","mock_http","connector_route"],"type":"string"}},"required":["type"],"type":"object"},"Run":{"properties":{"created_at":{"format":"date-time","type":"string"},"created_by":{"type":"string"},"expires_at":{"format":"date-time","type":"string"},"external_id":{"type":"string"},"finished_at":{"format":"date-time","type":"string"},"id":{"format":"uuid","type":"string"},"organisation_id":{"format":"uuid","type":"string"},"outcome":{"description":"Present once finished.","enum":["pass","fail","none"],"type":"string"},"project_id":{"format":"uuid","type":"string"},"reason":{"type":"string"},"started_at":{"format":"date-time","type":"string"},"status":{"enum":["active","finished","cancelled","expired"],"type":"string"},"suite_id":{"format":"uuid","type":"string"},"suite_version_id":{"format":"uuid","type":"string"},"ttl_seconds":{"type":"integer"}},"required":["id","organisation_id","project_id","suite_id","suite_version_id","status","ttl_seconds","started_at","expires_at","created_at"],"type":"object"},"RunWithResources":{"properties":{"resources":{"items":{"$ref":"#/components/schemas/Resource"},"type":"array"},"run":{"$ref":"#/components/schemas/Run"}},"required":["run","resources"],"type":"object"},"Signature":{"description":"A detached Ed25519 signature over named bytes, carrying the key it was made with.","properties":{"algorithm":{"enum":["ed25519"],"type":"string"},"key_id":{"type":"string"},"public_key":{"description":"Base64.","type":"string"},"sha256":{"description":"SHA-256 of the signed bytes.","type":"string"},"signature":{"description":"Base64.","type":"string"},"signed":{"description":"What was signed: manifest.json, or receipt.","type":"string"},"signed_at":{"format":"date-time","type":"string"}},"required":["algorithm","key_id","public_key","signed","sha256","signed_at","signature"],"type":"object"},"SignedReceipt":{"description":"The receipt and, on a platform with a signing key, its signature over the receipt's canonical JSON (objects with keys in byte order, no whitespace, numbers as written).","properties":{"receipt":{"$ref":"#/components/schemas/Receipt"},"signature":{"$ref":"#/components/schemas/Signature"}},"required":["receipt"],"type":"object"},"Suite":{"properties":{"archived_at":{"format":"date-time","type":"string"},"created_at":{"format":"date-time","type":"string"},"current_version":{"type":"integer"},"id":{"format":"uuid","type":"string"},"name":{"type":"string"},"organisation_id":{"format":"uuid","type":"string"},"project_id":{"format":"uuid","type":"string"},"slug":{"type":"string"}},"required":["id","organisation_id","project_id","slug","name","current_version","created_at"],"type":"object"},"SuiteVersion":{"properties":{"created_at":{"format":"date-time","type":"string"},"created_by":{"type":"string"},"definition":{"$ref":"#/components/schemas/Definition"},"definition_sha256":{"description":"SHA-256 of the canonical definition.","type":"string"},"id":{"format":"uuid","type":"string"},"organisation_id":{"format":"uuid","type":"string"},"suite_id":{"format":"uuid","type":"string"},"version":{"type":"integer"}},"required":["id","organisation_id","suite_id","version","definition","definition_sha256","created_at"],"type":"object"},"SuiteWithVersion":{"properties":{"suite":{"$ref":"#/components/schemas/Suite"},"version":{"$ref":"#/components/schemas/SuiteVersion"}},"required":["suite","version"],"type":"object"},"Usage":{"properties":{"billing":{"description":"The rig.interaction meter in the current billing period, from the billing ledger; null where interactions are not metered.","nullable":true,"properties":{"allowance":{"description":"Free interactions this period.","type":"integer"},"billable":{"description":"Interactions beyond the free allowance.","type":"integer"},"catalogue_version":{"description":"The rate catalogue version that prices the period.","type":"string"},"charge":{"description":"The exact charge so far in pounds before tax.","example":"0.00","type":"string"},"currency":{"const":"GBP","type":"string"},"exempt":{"description":"The account is exempt from settlement for Ironfang Rig.","type":"boolean"},"free_used":{"type":"integer"},"meter":{"const":"rig.interaction","type":"string"},"mode":{"description":"The billing account's mode: free accounts stop at the free allowance.","enum":["free","setup_pending","enabled","paused","closed"],"type":"string"},"period":{"properties":{"end":{"format":"date-time","type":"string"},"start":{"format":"date-time","type":"string"}},"required":["start","end"],"type":"object"},"remaining_free":{"type":"integer"},"reserved":{"description":"Interactions admitted and not yet settled; normally 0.","type":"integer"},"unit":{"example":"interaction","type":"string"},"unit_plural":{"example":"interactions","type":"string"},"used":{"description":"Interactions counted this period.","type":"integer"}},"required":["meter","unit","unit_plural","period","catalogue_version","mode","allowance","used","free_used","billable","reserved","remaining_free","charge","currency","exempt"],"type":"object"},"events":{"description":"Events recorded by the runs started in the window; by_type carries every type seen.","properties":{"by_type":{"additionalProperties":{"type":"integer"},"type":"object"},"callback_received":{"type":"integer"},"callback_replayed":{"type":"integer"},"connector_request_completed":{"type":"integer"},"email_received":{"type":"integer"},"fault_injected":{"type":"integer"},"mock_request_received":{"type":"integer"},"total":{"type":"integer"},"usage_refused":{"description":"Inbound interactions the billing ledger refused; they were not recorded.","type":"integer"}},"required":["total","email_received","callback_received","mock_request_received","connector_request_completed","callback_replayed","fault_injected","usage_refused","by_type"],"type":"object"},"period":{"properties":{"end":{"format":"date-time","type":"string"},"start":{"format":"date-time","type":"string"}},"required":["start","end"],"type":"object"},"runs":{"description":"Runs started in the window by how they ended, plus the number active right now.","properties":{"active_now":{"type":"integer"},"cancelled":{"type":"integer"},"expired":{"type":"integer"},"failed":{"type":"integer"},"finished":{"type":"integer"},"no_outcome":{"type":"integer"},"passed":{"type":"integer"},"started":{"type":"integer"}},"required":["started","active_now","finished","cancelled","expired","passed","failed","no_outcome"],"type":"object"}},"required":["period","runs","events","billing"],"type":"object"}},"securitySchemes":{"apiKey":{"bearerFormat":"if_live_ platform API key","description":"A platform API key minted in the portal (Warden). Its scopes are its\npermissions - `rig:read`, `rig:write`, `rig:run`,\n`rig:connector`, or `rig:*` - and it acts only in the organisation\nit was minted for. Keys without Rig scopes are refused with 403.\n","scheme":"bearer","type":"http"},"bearerAuth":{"bearerFormat":"Warden JWT","description":"A Warden user token. Each route also requires a tenant permission in\nthe acting tenant (`X-Ironfang-Tenant`), resolved live from Warden:\nreads need `rig.read`; creating and revising projects and suites\n`rig.write`; starting, finishing and cancelling runs and allocating\nresources `rig.run`. A missing permission is 403; a permission\nlookup failure is 503.\n","scheme":"bearer","type":"http"}}},"externalDocs":{"description":"Ironfang Rig documentation","url":"https://ironfang.com/rig/docs"},"info":{"contact":{"email":"hello@ironfang.com","name":"Ironfang","url":"https://ironfang.com/contact"},"description":"Ironfang Rig gives an application under test a disposable external\nworld: temporary inbox addresses, public callback URLs, mock HTTP\nendpoints and connector routes, allocated fresh for every run, with an\nappend-only, sequenced timeline of everything Ironfang observed.\n\nThe model is: suites persist, runs are disposable, resources have\nexplicit lifetimes. A suite is a versioned definition of resources and\nexpectations; a run is one execution of the suite's current version,\nlives for its TTL (default 30 minutes, at most 24 hours) and ends by\nbeing finished, cancelled or expired. Nothing here runs your code.\n\nWhat is billed is interactions: each accepted inbound email, inbound\ncallback and mock exchange is one `rig.interaction`, counted once when\nit is recorded. Creating projects, suites, runs and resources, and\nevery call to this API, is never metered. `getUsage` reports the meter\nand `getEnvironment` says what counts and how a refusal is answered.\n\nEverything is organisation-bound: an object in another organisation is\nreported as not found. Authenticate with an Ironfang platform API key\ncarrying Rig scopes, or a Warden user token. Base URL\nhttps://api.ironfang.com/rig.\n\nFor agents and tools: the canonical base URL is\nhttps://api.ironfang.com/rig and every operation has a stable\n`operationId`. Every error an operation returns is JSON of the shape\n`{\"error\": {\"code\", \"message\", \"docs\"}, \"request_id\"}`: branch on `code`\nand the HTTP status, never on `message`. The machine-readable index of the\nIronfang APIs is https://ironfang.com/.well-known/api-catalog, and\nhttps://ironfang.com/llms.txt is a plain-text map of them for a language\nmodel. Ironfang Rig is also available as MCP tools at\nhttps://mcp.ironfang.com/mcp (see https://ironfang.com/docs/mcp).\n","license":{"name":"Proprietary - use governed by the Ironfang terms of service","url":"https://ironfang.com/legal/terms"},"summary":"Disposable inboxes, callback URLs, mock endpoints and connector routes for integration tests.","termsOfService":"https://ironfang.com/legal/terms","title":"Ironfang Rig API","version":"0.1.0"},"openapi":"3.1.0","paths":{"/v1/environment":{"get":{"description":"The fixed facts of this deployment: the domain inbox addresses are minted under, the base URLs of callback and mock addresses, the gateway connectors dial, the ironfang-connect release to run when the API knows it, every per-organisation, per-suite and per-run bound, what is metered, and how long payload bytes and runs are kept. The bounds are capacity limits, the same whatever the account pays. Read-only. Needs rig.read.","operationId":"getEnvironment","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Environment"}}},"description":"The environment"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"The hosts, bounds and retention a client builds against","tags":["Usage"]}},"/v1/evidence/keys":{"get":{"description":"The public keys bundles and receipts are signed with, by key id, so a verifier can trust a signature by key rather than by the key it carries. Empty on a platform that signs nothing. Needs rig.read.","operationId":"listEvidenceKeys","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"keys":{"items":{"$ref":"#/components/schemas/PublicKey"},"type":"array"}},"required":["keys"],"type":"object"}}},"description":"The keys."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"The keys evidence is signed with","tags":["Evidence"]}},"/v1/home":{"get":{"description":"Counts of projects, suites and runs, the moments that mark setup (a key first reached the product, the first run started, the first run finished), and the seven most recent runs as activity. Labels carry no addresses, names or payloads. Needs rig.read.","operationId":"getProductHome","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"activity":{"items":{"properties":{"at":{"format":"date-time","type":"string"},"href":{"type":"string"},"id":{"format":"uuid","type":"string"},"issue":{"description":"Present when the run failed (error) or expired (warning).","enum":["error","warning"],"type":"string"},"label":{"type":"string"}},"required":["id","label","href","at"],"type":"object"},"maxItems":7,"type":"array"},"counts":{"additionalProperties":{"type":"integer"},"type":"object"},"first_value_at":{"format":"date-time","type":"string"},"integrated_at":{"format":"date-time","type":"string"},"key_at":{"format":"date-time","type":"string"},"last_activity_at":{"format":"date-time","type":"string"},"product":{"const":"rig","type":"string"}},"required":["product","counts","activity"],"type":"object"}}},"description":"Counts, timestamps and at most seven activity labels"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"Tenant-scoped setup facts and recent activity for the portal","tags":["Usage"]}},"/v1/projects":{"get":{"description":"Returns the organisation's projects, newest first, as a bounded keyset page. Read-only; no side effects.","operationId":"listProjects","parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"next_cursor":{"description":"Opaque cursor for the next page; empty on the last page.","type":"string"},"projects":{"items":{"$ref":"#/components/schemas/Project"},"type":"array"}},"required":["projects"],"type":"object"}}},"description":"A page of projects"},"400":{"$ref":"#/components/responses/InvalidQuery"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List projects","tags":["Projects"]},"post":{"description":"Creates a project. The slug is unique within the organisation and appears in nothing public. Needs rig.write.","operationId":"createProject","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"name":{"example":"Ironfang Platform","maxLength":120,"type":"string"},"slug":{"example":"ironfang-platform","pattern":"^[a-z0-9][a-z0-9-]{0,62}$","type":"string"}},"required":["slug","name"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"properties":{"project":{"$ref":"#/components/schemas/Project"}},"required":["project"],"type":"object"}}},"description":"Project created"},"400":{"$ref":"#/components/responses/InvalidJSON"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/InvalidRequest"}},"summary":"Create a project","tags":["Projects"]}},"/v1/projects/{projectId}":{"get":{"description":"Returns one project. Another organisation's project is not found.","operationId":"getProject","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"project":{"$ref":"#/components/schemas/Project"}},"required":["project"],"type":"object"}}},"description":"The project"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Get a project","tags":["Projects"]},"parameters":[{"$ref":"#/components/parameters/ProjectId"}]},"/v1/runs":{"get":{"description":"Returns the organisation's runs, newest first, with optional filters. Read-only.","operationId":"listRuns","parameters":[{"in":"query","name":"project","required":false,"schema":{"format":"uuid","type":"string"}},{"in":"query","name":"suite","required":false,"schema":{"format":"uuid","type":"string"}},{"in":"query","name":"status","required":false,"schema":{"enum":["active","finished","cancelled","expired"],"type":"string"}},{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"next_cursor":{"type":"string"},"runs":{"items":{"$ref":"#/components/schemas/Run"},"type":"array"}},"required":["runs"],"type":"object"}}},"description":"A page of runs"},"400":{"$ref":"#/components/responses/InvalidQuery"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List runs","tags":["Runs"]}},"/v1/runs/{runId}":{"get":{"description":"Returns one run's state, lifetime and outcome.","operationId":"getRun","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"run":{"$ref":"#/components/schemas/Run"}},"required":["run"],"type":"object"}}},"description":"The run"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Get a run","tags":["Runs"]},"parameters":[{"$ref":"#/components/parameters/RunId"}]},"/v1/runs/{runId}/cancel":{"parameters":[{"$ref":"#/components/parameters/RunId"}],"post":{"description":"Ends an active run without an outcome and writes run.cancelled. A run that has already ended answers 409. Needs rig.run.","operationId":"cancelRun","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"reason":{"maxLength":200,"type":"string"}},"type":"object"}}},"required":false},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"run":{"$ref":"#/components/schemas/Run"}},"required":["run"],"type":"object"}}},"description":"The cancelled run"},"400":{"$ref":"#/components/responses/InvalidJSON"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/RunNotActive"},"422":{"$ref":"#/components/responses/InvalidRequest"}},"summary":"Cancel a run","tags":["Runs"]}},"/v1/runs/{runId}/connectors":{"get":{"description":"Returns every connector minted for the run with its state (pending, online, offline), the routes it is bound to and when it was last seen, plus how many deliveries are still waiting for a connector. Neither credential is ever returned here. Read-only.","operationId":"listConnectors","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"connectors":{"items":{"$ref":"#/components/schemas/Connector"},"type":"array"},"online":{"description":"How many connectors hold a session now.","type":"integer"},"pending_deliveries":{"description":"Callbacks queued for a route that no session has answered yet.","type":"integer"}},"required":["connectors","online","pending_deliveries"],"type":"object"}}},"description":"The connectors"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"List a run's connectors","tags":["Connectors"]},"parameters":[{"$ref":"#/components/parameters/RunId"}],"post":{"description":"Mints a connector on an active run and returns its bootstrap token once. The token is single-use, expires after ten minutes, and is bound to this organisation, this run and the route labels named (every label the run declares when none are given). Start `ironfang-connect` with it and a local target for each label; the connector exchanges it at the gateway for a session credential that lives no longer than the run. The platform only ever names a route label; what a label points at is decided on the connector's command line and never leaves the machine. Needs rig.connector. A run may have at most 16 connectors.","operationId":"createConnector","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"name":{"description":"A label for this connector in the timeline, such as the machine it runs on.","maxLength":64,"type":"string"},"routes":{"description":"Route labels to bind the credential to; a subset of what the run declares. Default all of them.","items":{"pattern":"^[a-z0-9][a-z0-9-]{0,62}$","type":"string"},"maxItems":32,"type":"array"}},"type":"object"}}},"required":false},"responses":{"201":{"content":{"application/json":{"schema":{"properties":{"bootstrap_expires_at":{"format":"date-time","type":"string"},"bootstrap_token":{"description":"The single-use credential, prefixed `ift_boot_`. Pass it to ironfang-connect in `IRONFANG_CONNECT_TOKEN`.","type":"string"},"command":{"description":"The command line to complete with local targets.","type":"string"},"connector":{"$ref":"#/components/schemas/Connector"},"gateway_url":{"description":"The WebSocket endpoint the connector dials.","format":"uri","type":"string"},"release":{"description":"The ironfang-connect build to run, when the API knows it.","properties":{"checksums_url":{"description":"SHA-256 sums of every binary of that version.","format":"uri","type":"string"},"version":{"type":"string"}},"required":["version","checksums_url"],"type":"object"},"routes":{"items":{"type":"string"},"type":"array"}},"required":["connector","bootstrap_token","bootstrap_expires_at","gateway_url","routes","command"],"type":"object"}}},"description":"Connector minted; the bootstrap token appears here and nowhere else"},"400":{"$ref":"#/components/responses/InvalidJSON"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/RunNotActive"},"422":{"$ref":"#/components/responses/InvalidRequest"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The run has its limit of connectors"}},"summary":"Mint a connector and its bootstrap token","tags":["Connectors"]}},"/v1/runs/{runId}/events":{"get":{"description":"Returns the run's events in sequence order after `since`, which is a sequence number (0 for the start). Sequences are contiguous from 1 and never change: the same request always returns the same events, so a client can page deterministically with `next_since`. Payloads larger than an event carries are referenced by blob_ref, never embedded. Read-only.","operationId":"listEvents","parameters":[{"description":"Return events with a sequence greater than this.","in":"query","name":"since","required":false,"schema":{"default":0,"minimum":0,"type":"integer"}},{"in":"query","name":"limit","required":false,"schema":{"default":100,"maximum":500,"minimum":1,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"events":{"items":{"$ref":"#/components/schemas/Event"},"type":"array"},"next_since":{"description":"The last sequence returned; pass it as since for the next page.","type":"integer"}},"required":["events","next_since"],"type":"object"}}},"description":"Events in sequence order"},"400":{"$ref":"#/components/responses/InvalidQuery"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Read the timeline","tags":["Events"]},"parameters":[{"$ref":"#/components/parameters/RunId"}]},"/v1/runs/{runId}/events/{eventId}":{"get":{"description":"Returns one event of the run with its full data.","operationId":"getEvent","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"event":{"$ref":"#/components/schemas/Event"}},"required":["event"],"type":"object"}}},"description":"The event"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Get an event","tags":["Events"]},"parameters":[{"$ref":"#/components/parameters/RunId"},{"$ref":"#/components/parameters/EventId"}]},"/v1/runs/{runId}/events/{eventId}/payload":{"get":{"description":"The exact bytes a callback carried, as an opaque attachment whatever the sender declared, so third-party content is never rendered on the API origin. The recorded media type is in `X-Ironfang-Payload-Media-Type` and the SHA-256 in `X-Ironfang-Payload-SHA256`; the same digest is in the event's data. Events without a payload (anything but a received callback) are not found.","operationId":"getEventPayload","responses":{"200":{"content":{"application/octet-stream":{"schema":{"format":"binary","type":"string"}}},"description":"The payload bytes","headers":{"X-Ironfang-Payload-Media-Type":{"description":"The Content-Type the sender declared.","schema":{"type":"string"}},"X-Ironfang-Payload-SHA256":{"description":"Hex SHA-256 of the bytes.","schema":{"type":"string"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"410":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The payload's bytes were retired by retention; the event and its digest remain"}},"summary":"Download an event's payload","tags":["Events"]},"parameters":[{"$ref":"#/components/parameters/RunId"},{"$ref":"#/components/parameters/EventId"}]},"/v1/runs/{runId}/events/{eventId}/replay":{"parameters":[{"$ref":"#/components/parameters/RunId"},{"$ref":"#/components/parameters/EventId"}],"post":{"description":"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. The replay is recorded as `callback.replayed`, then travels like any delivery: the timeline shows its own `callback.forwarded` and `connector.request.completed` against the original event id. Armed faults do not act on a replay; it is the test's explicit act. At most 200 replays per run. Needs rig.run.","operationId":"replayCallback","responses":{"202":{"content":{"application/json":{"schema":{"properties":{"delivery_id":{"description":"The delivery the connector will receive; its completion event names it.","format":"uuid","type":"string"},"event":{"$ref":"#/components/schemas/Event"}},"required":["event","delivery_id"],"type":"object"}}},"description":"The replay is queued for the route"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/RunNotActive"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The event is not a callback on a resource with a connector route"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The run has replayed its limit of callbacks"}},"summary":"Replay a recorded callback","tags":["Events"]}},"/v1/runs/{runId}/evidence":{"parameters":[{"$ref":"#/components/parameters/RunId"}],"post":{"description":"Builds the run's evidence bundle from what it recorded and returns it: a ZIP holding `manifest.json` (the run, counts, and every file's size and SHA-256), `events.json` (the whole timeline in sequence order), `expectations.json` (each expectation with its verdict once the run is finished), `faults.json`, `resources.json`, `definition.json`, every message under `messages/` and every request body under `requests/`, named by sequence and event id. Bounded at 10,000 events and 256 MiB of payloads; the manifest says when a bound bit or when retention had already taken some bytes. With `Accept: application/json` the manifest alone is returned. Read-only: the bundle is what the caller can already read event by event.","operationId":"exportEvidence","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"download":{"description":"The path that returns the bundle itself.","type":"string"},"manifest":{"additionalProperties":true,"type":"object"}},"required":["manifest","download"],"type":"object"}},"application/zip":{"schema":{"format":"binary","type":"string"}}},"description":"The bundle, or its manifest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Export the run's evidence bundle","tags":["Events"]}},"/v1/runs/{runId}/faults":{"get":{"description":"Returns every fault armed on the run - active, spent and removed - in the order they were armed, with how often each has fired. The fault history is evidence and is kept with the run. Read-only.","operationId":"listFaults","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"faults":{"items":{"$ref":"#/components/schemas/Fault"},"type":"array"}},"required":["faults"],"type":"object"}}},"description":"The faults"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"List a run's faults","tags":["Faults"]},"parameters":[{"$ref":"#/components/parameters/RunId"}],"post":{"description":"Arms deterministic interference on one resource of an active run. `delay`, `duplicate` and `drop` act on the forwarding of a callback resource that has a connector route: the delivery is held for the duration, made `copies` extra times, or recorded and never forwarded. `status_override` makes a `mock_http` resource answer the given status whatever its rules said, body and headers unchanged. A fault 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. One active fault of each type per resource. Needs rig.run. No randomness: the same faults against the same traffic give the same timeline.","operationId":"addFault","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FaultSpec"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"properties":{"fault":{"$ref":"#/components/schemas/Fault"}},"required":["fault"],"type":"object"}}},"description":"Fault armed"},"400":{"$ref":"#/components/responses/InvalidJSON"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The run has ended, or an active fault of that type is already armed on the resource"},"422":{"$ref":"#/components/responses/InvalidRequest"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The run has its limit of faults"}},"summary":"Arm a fault","tags":["Faults"]}},"/v1/runs/{runId}/faults/{faultId}":{"delete":{"description":"Disarms an active fault and records `fault.removed`. A fault that is already spent or removed is returned as it is. The row is kept: the fault history is evidence. Needs rig.run.","operationId":"removeFault","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"fault":{"$ref":"#/components/schemas/Fault"}},"required":["fault"],"type":"object"}}},"description":"The fault, now removed"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/RunNotActive"}},"summary":"Disarm a fault","tags":["Faults"]},"parameters":[{"$ref":"#/components/parameters/RunId"},{"$ref":"#/components/parameters/FaultId"}]},"/v1/runs/{runId}/finish":{"parameters":[{"$ref":"#/components/parameters/RunId"}],"post":{"description":"Ends an active run with the outcome you report (pass, fail, or none when you make no claim) and writes run.completed to its timeline. Exactly once: a run that has already ended answers 409. Needs rig.run.","operationId":"finishRun","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"outcome":{"default":"none","enum":["pass","fail","none"],"type":"string"},"reason":{"maxLength":200,"type":"string"}},"type":"object"}}},"required":false},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"expectations":{"description":"The verdict on each of the suite's expectations, in the order declared.","items":{"$ref":"#/components/schemas/ExpectationResult"},"type":"array"},"run":{"$ref":"#/components/schemas/Run"}},"required":["run","expectations"],"type":"object"}}},"description":"The finished run"},"400":{"$ref":"#/components/responses/InvalidJSON"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/RunNotActive"},"422":{"$ref":"#/components/responses/InvalidRequest"}},"summary":"Finish a run","tags":["Runs"]}},"/v1/runs/{runId}/hold":{"delete":{"description":"Recorded as retention.released. A run that was not held is returned as it is. Needs rig.run.","operationId":"releaseRun","parameters":[{"$ref":"#/components/parameters/RunId"}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"run":{"$ref":"#/components/schemas/Run"}},"required":["run"],"type":"object"}}},"description":"The run without its hold."},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Lift a retention hold","tags":["Runs"]},"put":{"description":"Retention leaves the run, its events and its payloads alone until the date, at most a year ahead. Holding again replaces the date. Recorded as retention.held on the run's timeline. Needs rig.run.","operationId":"holdRun","parameters":[{"$ref":"#/components/parameters/RunId"}],"requestBody":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"until":{"format":"date-time","type":"string"}},"required":["until"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"run":{"$ref":"#/components/schemas/Run"}},"required":["run"],"type":"object"}}},"description":"The run with its hold."},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/InvalidRequest"}},"summary":"Keep the run from retention until a date","tags":["Runs"]}},"/v1/runs/{runId}/receipt":{"post":{"description":"A signed statement of what the run was and how it ended: project, suite and version, status and outcome, every verdict, the event count and the head of the event hash chain. Signed with the platform's Ed25519 key when it has one; the signature carries the key, and /v1/evidence/keys publishes it, so the receipt verifies anywhere. A run whose chain does not recompute gets no receipt (409). Needs rig.read.","operationId":"createRunReceipt","parameters":[{"$ref":"#/components/parameters/RunId"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignedReceipt"}}},"description":"The receipt and its signature."},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}},"summary":"A signed receipt for the run","tags":["Evidence"]}},"/v1/runs/{runId}/resources":{"get":{"description":"Returns every resource allocated to the run, in allocation order, with the address or URL each one answers at. Read-only.","operationId":"listResources","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"resources":{"items":{"$ref":"#/components/schemas/Resource"},"type":"array"}},"required":["resources"],"type":"object"}}},"description":"The resources"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"List a run's resources","tags":["Resources"]},"parameters":[{"$ref":"#/components/parameters/RunId"}],"post":{"description":"Allocates one more resource on an active run, beyond what the suite declared, with the run's expiry. Names are unique within the run. Needs rig.run. Public effect: the address or URL is reachable from the Internet for the rest of the run.","operationId":"createResource","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"connector":{"$ref":"#/components/schemas/ConnectorSpec"},"name":{"example":"shipping_api","pattern":"^[a-z][a-z0-9_]{0,63}$","type":"string"},"route":{"description":"The route label of a connector_route.","type":"string"},"rules":{"description":"For a mock_http, the rules it answers with.","items":{"$ref":"#/components/schemas/MockRule"},"maxItems":32,"type":"array"},"type":{"enum":["email","callback","mock_http","connector_route"],"type":"string"}},"required":["name","type"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"properties":{"resource":{"$ref":"#/components/schemas/Resource"}},"required":["resource"],"type":"object"}}},"description":"Resource allocated"},"400":{"$ref":"#/components/responses/InvalidJSON"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/RunNotActive"},"422":{"$ref":"#/components/responses/InvalidRequest"}},"summary":"Allocate a resource","tags":["Resources"]}},"/v1/runs/{runId}/resources/{resourceId}/mock":{"parameters":[{"$ref":"#/components/parameters/RunId"},{"$ref":"#/components/parameters/ResourceId"}],"put":{"description":"Replaces the rules a mock_http resource answers with, in order, while the run is active. The first rule whose match holds decides the response; no match answers 404 with code `no_mock_rule`. The change is recorded on the timeline as `mock.rules.updated`. Needs rig.run.","operationId":"setMockRules","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"rules":{"items":{"$ref":"#/components/schemas/MockRule"},"maxItems":32,"type":"array"}},"required":["rules"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"resource":{"$ref":"#/components/schemas/Resource"}},"required":["resource"],"type":"object"}}},"description":"The resource with its new rules"},"400":{"$ref":"#/components/responses/InvalidJSON"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/RunNotActive"},"422":{"$ref":"#/components/responses/InvalidRequest"}},"summary":"Replace a mock's rules","tags":["Resources"]}},"/v1/runs/{runId}/wait":{"parameters":[{"$ref":"#/components/parameters/RunId"}],"post":{"description":"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. 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 are 1s to 90s (default 30s); loop with `next_since` for longer. `match` keys are top-level fields of the event's data: `field` compares as JSON, `field_contains` requires a string containing the value. Deterministic: the same timeline and request always answer the same event. At most 16 waits per organisation may be open at once.","operationId":"waitForEvent","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"match":{"additionalProperties":true,"example":{"method":"POST"},"maxProperties":16,"type":"object"},"resource":{"description":"Limit to one of the run's resources, by name.","type":"string"},"since":{"default":0,"description":"Consider only events with a greater sequence.","minimum":0,"type":"integer"},"timeout":{"default":"30s","example":"30s","type":"string"},"type":{"example":"callback.received","type":"string"}},"required":["type"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"event":{"$ref":"#/components/schemas/Event"},"matched":{"type":"boolean"},"next_since":{"description":"The last sequence examined; pass it as since to continue.","type":"integer"},"waited_ms":{"type":"integer"}},"required":["matched","next_since","waited_ms"],"type":"object"}}},"description":"Matched, or timed out without a match"},"400":{"$ref":"#/components/responses/InvalidJSON"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/InvalidRequest"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Too many waits are open for this organisation (`too_many_waiters`); retry after a moment."}},"summary":"Wait for an event","tags":["Events"]}},"/v1/suites":{"get":{"description":"Returns the organisation's suites, newest first, optionally within one project. Read-only.","operationId":"listSuites","parameters":[{"description":"Limit to one project by id.","in":"query","name":"project","required":false,"schema":{"format":"uuid","type":"string"}},{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"next_cursor":{"type":"string"},"suites":{"items":{"$ref":"#/components/schemas/Suite"},"type":"array"}},"required":["suites"],"type":"object"}}},"description":"A page of suites"},"400":{"$ref":"#/components/responses/InvalidQuery"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List suites","tags":["Suites"]},"post":{"description":"Creates a suite and its first version from a definition. The definition is validated in full and stored canonically; its SHA-256 identifies the version. The slug is unique within the project. Needs rig.write.","operationId":"createSuite","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"definition":{"$ref":"#/components/schemas/Definition"},"name":{"example":"Signup flow","maxLength":120,"type":"string"},"project_id":{"format":"uuid","type":"string"},"slug":{"example":"signup","pattern":"^[a-z0-9][a-z0-9-]{0,62}$","type":"string"}},"required":["project_id","slug","name","definition"],"type":"object"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuiteWithVersion"}}},"description":"Suite created with version 1"},"400":{"$ref":"#/components/responses/InvalidJSON"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/InvalidRequest"}},"summary":"Create a suite","tags":["Suites"]}},"/v1/suites/{suiteId}":{"get":{"description":"Returns the suite and its current version.","operationId":"getSuite","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuiteWithVersion"}}},"description":"The suite and its current version"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Get a suite","tags":["Suites"]},"parameters":[{"$ref":"#/components/parameters/SuiteId"}],"put":{"description":"Records a new version when the definition changed and answers the current version unchanged when it did not, so sending the same document twice creates one version. Existing runs keep the version they were created from. Needs rig.write.","operationId":"reviseSuite","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"definition":{"$ref":"#/components/schemas/Definition"},"name":{"description":"A new display name; omit to keep the current one.","maxLength":120,"type":"string"}},"required":["definition"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuiteWithVersion"},{"properties":{"revised":{"description":"Whether anything changed.","type":"boolean"}},"type":"object"}]}}},"description":"The suite and the version now current"},"400":{"$ref":"#/components/responses/InvalidJSON"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/InvalidRequest"}},"summary":"Revise a suite","tags":["Suites"]}},"/v1/suites/{suiteId}/runs":{"parameters":[{"$ref":"#/components/parameters/SuiteId"}],"post":{"description":"Starts a run of the suite's current version and allocates every resource the definition declares: a fresh inbox address, callback URL, mock URL or route label each, unguessable and unique to this run. The run is active until it is finished, cancelled or its TTL passes, after which its resources stop accepting activity and its timeline is read-only. Send an Idempotency-Key to make a retry return the run it already started (200 rather than 201). An organisation may have at most 50 active runs at once (a run is active until it is finished, cancelled or its TTL passes); the next start is refused with 429 `active_run_limit` until one ends. The limit is capacity, the same on every account. Needs rig.run. Public effect: the allocated addresses and URLs are reachable from the Internet for the life of the run.","operationId":"createRun","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"external_id":{"description":"Your own reference, such as a CI run id.","maxLength":120,"type":"string"},"reason":{"maxLength":200,"type":"string"},"ttl":{"description":"Run lifetime as a duration (\"30m\", \"2h\"), 1m to 24h; defaults to the suite's defaults.run_ttl.","example":"30m","type":"string"}},"type":"object"}}},"required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunWithResources"}}},"description":"The run this Idempotency-Key already started"},"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunWithResources"}}},"description":"Run started and resources allocated"},"400":{"$ref":"#/components/responses/InvalidJSON"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/InvalidRequest"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The organisation already has its limit of 50 active runs (`active_run_limit`); finish or cancel one to start another"}},"summary":"Start a run","tags":["Runs"]}},"/v1/suites/{suiteId}/versions":{"get":{"description":"Returns up to 100 versions, newest first, each with its full definition.","operationId":"listSuiteVersions","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"versions":{"items":{"$ref":"#/components/schemas/SuiteVersion"},"type":"array"}},"required":["versions"],"type":"object"}}},"description":"The versions"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"List a suite's versions","tags":["Suites"]},"parameters":[{"$ref":"#/components/parameters/SuiteId"}]},"/v1/usage":{"get":{"description":"Counts the organisation's runs started in the window and the events those runs recorded, by type, plus how many runs are active now. The window defaults to the current UTC calendar month to date and may span at most 92 days. Where unified billing runs, `billing` adds the organisation's `rig.interaction` meter for the current billing period (the UTC calendar month, whatever the window) from the billing ledger: the free allowance, how many interactions were used and are reserved, and the charge so far before tax. One interaction is one accepted inbound email, inbound callback or mock exchange with its response; a transport retry carrying the same identity (an email's Message-ID with the same bytes, a callback's `Idempotency-Key` or `Webhook-Id` with the same request) counts once, so `used` can be below the event counts. Creating projects, suites, runs and resources, faults, replays, waits, reads and assertions are never metered. `billing` is null where interactions are not metered. Reading usage is never metered. Needs rig.read.","operationId":"getUsage","parameters":[{"description":"Start of the window, RFC 3339; default the first of the current UTC month.","in":"query","name":"since","required":false,"schema":{"format":"date-time","type":"string"}},{"description":"End of the window, RFC 3339, exclusive; default now.","in":"query","name":"until","required":false,"schema":{"format":"date-time","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Usage"}}},"description":"The counts"},"400":{"$ref":"#/components/responses/InvalidQuery"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"Runs, events and metered interactions","tags":["Usage"]}}},"security":[{"apiKey":[]},{"bearerAuth":[]}],"servers":[{"url":"https://api.ironfang.com/rig"}],"tags":[{"description":"Persistent containers for one application or product.","name":"Projects"},{"description":"Persistent, versioned test definitions.","name":"Suites"},{"description":"Disposable executions of a suite version.","name":"Runs"},{"description":"The external identities a run receives.","name":"Resources"},{"description":"Deterministic interference armed on a run's resources.","name":"Faults"},{"description":"Outbound connectors that forward a run's callbacks to fixed local routes.","name":"Connectors"},{"description":"The run timeline.","name":"Events"},{"description":"What the organisation has used, and the deployment's fixed facts.","name":"Usage"},{"description":"Bundles, receipts and the keys they are signed with.","name":"Evidence"}]}