---
title: "Open a receipt before a side effect"
method: POST
path: "/v1/receipts"
---

# Open a receipt before a side effect

`POST /v1/receipts`

## Request

- Base URL: `https://api.doubleoh.ai`
- URL: `https://api.doubleoh.ai/v1/receipts`
- Auth: one of:
  - API key in header `x-api-key`
  - HTTP bearer

Rules are checked first. 201: allowed, do it and confirm. 200 with `duplicate: true`: this idempotency key was already used, do not do it again. 202: a person must approve first, wait on `waitUrl`; when they answer, the receipt becomes `approved` (do it, then confirm) or `refused`. 402 `quota_exceeded`: it needs a person and the plan has no fixes left; the receipt is recorded as needs_person, do not do it. 403 `authority_refused`: a rule forbids it. 409 `not_approved`: `approvedBy` does not cover this action. Unconfirmed past `deadlineAt`, a person is asked to check. The same idempotencyKey after `failed` reopens the same receipt (201, `retried: true`), keeping its approval; `refused` is final. Limits count a running total per agent and currency over their window (a day unless the rule says otherwise); several small ones that pass it together come back with `authority.pattern: "split"`. 429: a person must decide but this agent may not open another wall now (`rate_limited`, `agent_paused`); the receipt is recorded as needs_person, do not do it, retry after `retryAfterSeconds`.

## Request body

- object
  - `tool` string, required
  - `action` string, required: What it does, in one sentence.
  - `args` unknown
  - `target` string
  - `agent` string: Optional. The key's own agent wins; another name on a key bound to an agent is 400 agent_mismatch.
  - `idempotencyKey` string: One per real-world action, the same on every retry. Defaults to tool + args hash + the minute.
  - `amountCents` integer
  - `currency` string
  - `confirmWithinSeconds` integer
  - `approvedBy` string: An intervention id a person answered with approved: true in the last hour, on this key, asked by the same agent, for the same tool and action (and site, when it had one), and for at least this amount in the same currency (a receipt with an amount needs a yes that had one, and a yes with an amount needs the receipt to send it). One approval backs exactly one receipt; a second use is 409 not_approved. The receipt opens as approved, with no new wall and no second page.

## Response `200`

Duplicate: already done or in progress.

- object
  - `receipt` object
    - `id` string
    - `state` 'pending' | 'needs_person' | 'confirmed' | 'failed' | 'unconfirmed' | 'refused' | 'approved' | 'held': approved: a person said yes; do it, then confirm (with approval.correctedArgs when a person changed the call). refused: a rule or a person said no; approval.reason says why when it was a person. held: allowed, but held for the team until heldUntil; GET it with ?wait=55 until it changes.
    - `tool` string
    - `target` string, nullable
    - `action` string
    - `agent` string, nullable: Who acted: the key's agent, else the label sent.
    - `agentName` string, nullable: Verified: the agent this key belongs to (one key per agent). Null on a legacy key.
    - `claimedAgent` string, nullable: A label the request sent that is not the key's agent (a legacy key's self-reported name, or another name a bound key sent on a read). Unverified.
    - `idempotencyKey` string
    - `amountCents` integer, nullable
    - `currency` string, nullable
    - `evidence` object, nullable
    - `authority` object, nullable
      - `decision` string
      - `ruleId` string
      - `sentence` string
      - `pattern` 'split': split: this looks like one payment cut into smaller ones (several in a short time that together pass a limit). Limits count a running total per agent and currency over their window.
    - `approval` object, nullable: When a person had to decide: their yes or no, why, who, when, and the intervention it was given on.
      - `approved` boolean
      - `reason` string, nullable
      - `by` string, nullable
      - `at` string, date-time
      - `interventionId` string
      - `amountCents` integer, nullable: How much the person approved, which may be less than the receipt's amountCents.
      - `currency` string, nullable
    - `deadlineAt` string, date-time
    - `confirmedAt` string, date-time, nullable
    - `interventionId` string, nullable
  - `duplicate` boolean
  - `interventionId` string, nullable: Always present: the wall this receipt waits on or was approved through, else null.
  - `waitUrl` string: When it is still waiting on a person.
  - `confirmUrl` string
  - `next` string

## Other responses

- `201`: Allowed: do it, then POST confirmUrl.
- `202`: Needs a person: wait on waitUrl.
- `400`: Bad input, or `agent_mismatch`: this key belongs to another agent.
- `402`: `quota_exceeded`: needs a person and the organisation has no human fixes left. Carries `upgradeUrl` and the recorded `receipt`.
- `403`: A rule forbids it.
- `409`: `not_approved`: approvedBy does not cover this action; `differs` names what does not match, and `approved` the amount it did cover.
- `429`: `rate_limited` or `agent_paused`: it needs a person and this agent may not open a wall now. Carries `retryAfterSeconds` and the recorded `receipt`; do not do it.

---

[API](https://skmtc.dev/doubleoh/apis/doubleoh-api.md) · [All operations](https://skmtc.dev/doubleoh/apis/doubleoh-api/llms.txt) · [OpenAPI document](https://skmtc.dev/doubleoh/apis/doubleoh-api/revisions/398fb6ef5068?raw)
