Skip to content
OUTIS DOCS

Requests

A request is one ask: an action, who’s asking, and the params the operators should see. Your service creates it and reads it back.

{
  "id":             "req-4f2a9c1b8d7e6f50",
  "action":         "payments.deploy",
  "requester":      "keith",
  "operation_hash": "sha256:e9d75458c3d5401ac8230f42b291ed231873183cb94ba125915eb6b034fd1dfc",
  "state":          "succeeded",
  "live":           false,
  "outcome":        "authorized",
  "approvers":      ["maya", "sam"],
  "params":         { "repo": "acme/payments-api", "env": "production", "sha": "8d93f71" },
  "created_at":     <epoch ms>,
  "decided_at":     <epoch ms>,
  "intent":         null,
  "execution":      { "state": "none", ...the rest null... }
}
FIELDWHAT IT IS
idThe request id.
actionThe action id the request was created for.
requesterThe operator the request was made in the name of.
operation_hashA fingerprint of what the operators authorized: the action and params, fixed when the request was created. Recompute it from the operation you're about to run and refuse on a mismatch. The operation hash concept page has the exact preimage.
stateWhere the request is. The values are below.
liveWhether the request can still transition. An authorized request stays live while its integration makes its one call, then ends succeeded or failed.
outcomeNull until there's a verdict; one of the outcomes once there is.
approversThe operators who committed. Empty unless authorized.
paramsThe request's attributes as proposed.
created_atWhen the request was created.
decided_atWhen the verdict was reached. Null while live.
intentThe sealed intent the request was created with, verbatim.
executionWhere the intent stands. `state` is `none` on a request without one.
STATEMEANS
proposedCreated. Codes have not been minted and nobody has been told.
notifiedCodes are out. Nobody has staged, or a coincident round collapsed and every code was re-issued.
stagingAt least one operator has turned a key and typed their code.
authorizedEnough distinct operators committed. This is a fact about people, and it is not execution.
executingHanded to the integration's executor.
succeededTerminal. The side effect happened.
failedTerminal, and authorized. The ceremony worked; the thing it authorized did not.
expiredTerminal. The request TTL elapsed during staging.
abortedTerminal. Somebody declined with the ABORT key, or a coincident round was restarted.
deniedTerminal. Refused upstream.

Outis keeps its own state names. If your system thinks in the usual words, here’s the mapping:

Your word Outis states
pending proposed, notified, staging
approved authorized, then succeeded
rejected denied
cancelled aborted
expired expired

executing, succeeded and failed describe an integration’s call to the system that asked, like GitHub merging a pull request. A request for an action with no such integration, including every action your organization defined itself, goes from authorized to succeeded with nothing run. failed only happens when that integration’s call was refused.

OUTCOMEMEANS
authorizedEnough distinct operators turned their keys inside the window. Act on it.
deniedAn operator refused it.
expiredThe request TTL ran out with the quorum unmet.
abortedWithdrawn, by an operator's ABORT key or by the system.
POST /v1/requests

Requires a key holding propose. Answers 202 with the request object, wrapped the same way GET /v1/requests/{id} wraps it, once the eligible operators have been asked.

FIELDWHAT IT IS
actionrequiredAn action your organization holds. Its policy decides who's asked, how many must agree and in which mode.
requesterrequiredThe operator the request is made in the name of. Where the action denies self approval, this person is struck from their own quorum.
paramsoptionalA flat string map, at most 24 entries, keys 1 to 64 characters and values at most 512. A key can't start with `outis.`. It's what the operators see, it's covered by `operation_hash`, and it comes back on the decision unchanged.
summaryoptionalOne line a person should read: a commit subject, a pull request title. Whoever renders it escapes and caps it.
callback_urloptionalWhere to post the decision instead of polling for it. An absolute https URL, or http to a loopback address on a bench. Signed under the API key this request arrived with. Absent means you poll.
quorumoptionalHow many distinct operators must turn a key. Accepted only when the action marks its quorum selectable, and only within its bounds; anything else is a 422. Absent takes the action's default, or the quorum its param rules answer for these params. On an action with param rules that answer is a floor (the default when no rule matches), and a quorum below it is a 422 too.
intentoptionalA sealed call for your worker to run once the request is authorized, at most 64 KiB. Needs `params.intent`, the plaintext's digest. Outis stores it verbatim and can't open it.
execute_withinoptionalSeconds after the verdict a worker may still claim the intent. Only with an intent. Absent is seven days; the most is thirty.

intent and execute_within are for a request whose call your own worker runs once it’s authorized. Execution covers the envelope, the digest in params.intent, and how a worker claims and reports it.

Header What it is
Idempotency-Key optional 1 to 255 printable ASCII characters naming this operation from your side. The same key with the same action and params returns the original request with Idempotent-Replayed: true. The same key with anything else is a 409, kind idempotency_conflict.
curl -X POST "https://api.outis.tech/v1/requests" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OUTIS_KEY" \
  -H "Idempotency-Key: deploy-acme-payments-api-8d93f71" \
  -d '{
    "action":    "payments.deploy",
    "requester": "keith",
    "params":    { "repo": "acme/payments-api", "env": "production", "sha": "8d93f71" },
    "callback_url": "https://ci.example.com/outis/decision"
  }'
{
  "server_now": <epoch ms>,
  "request": { ...the request object, state notified, outcome null... }
}

On a replayed Idempotency-Key it’s the original request, in whatever state it’s reached, and the response carries Idempotent-Replayed: true.

GET /v1/requests/{id}

Requires a key holding read. Answers the request object wrapped with the server’s clock. The same request object is posted to the request’s callback URL, if it named one, inside each event.

curl "https://api.outis.tech/v1/requests/req-4f2a9c1b8d7e6f50" \
  -H "Authorization: Bearer $OUTIS_KEY"
{
  "server_now": <epoch ms>,
  "request": { ...the request object... }
}

A request that belongs to another organization is a 404, the same as one that doesn’t exist.