> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.ediphai.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.ediphai.com/_mcp/server.

# Delivery and retries

> Keep records until Ediphai has accepted them and recover after an outage.

Keep a durable outbox on your server. Key each request by environment, `record_type`, `record_id` and `revision`, and store the complete body before sending. Test and live delivery state must stay separate.

For a relational app, the core storage can be as small as this. The exact SQL types and locking syntax depend on your database:

```sql
CREATE TABLE ediphai_outbox (
  environment TEXT NOT NULL,
  record_type TEXT NOT NULL,
  record_id TEXT NOT NULL,
  revision BIGINT NOT NULL,
  request_json JSONB NOT NULL,
  state TEXT NOT NULL DEFAULT 'pending',
  next_attempt_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  attempts INTEGER NOT NULL DEFAULT 0,
  receipt_json JSONB,
  PRIMARY KEY (environment, record_type, record_id, revision)
);
```

Insert the outbox row in the same transaction that saves the source record. A worker claims due rows with a database lock, sends the stored body unchanged and saves the receipt. A test receipt never marks a live row sent.

## Handle a send

* **Accepted — HTTP 202, `outcome: "accepted"`:** Store the durable receipt. Check status later if you need processing progress.
* **Duplicate — HTTP 200, `outcome: "duplicate"`:** Store the receipt; the same type, ID, revision and body was already received.
* **Revision conflict — HTTP 409, `rule: "revision_conflict"`:** Investigate the changed body; a correction needs a higher revision.
* **Deleted record — HTTP 409, `rule: "deleted"`:** Stop. No revision of a deleted record ID is accepted.
* **Invalid request — HTTP 422 or another non-retryable problem:** Use its `rule`, JSON pointer and `fix` to correct the request before sending again.
* **Retryable response — HTTP 429, 503 or `retryable: true`:** Retry the unchanged request with backoff. Honor `Retry-After` or `retry_after_ms` when present.
* **Timeout or lost connection:** The API may have accepted the request. Retry the same body, ID and revision.

`record_id` stays stable for one source record. Raise `revision` when that record changes; a newer revision supersedes the earlier one. Do not create a new ID to hide a conflict. Set an HTTP timeout, but never treat it as proof that the API did not receive the request.

Read responses leniently. Ignore fields you don't know, and treat an unfamiliar receipt state or error rule as nonterminal while you check the current docs. Don't validate API responses against a strict frozen schema. Ediphai may add response fields without changing `/v1`.

## Recover missing receipts

Use `GET /v1/records/{record_type}/{record_id}` to check a known record. `GET /v1/records?since=...` lists each record at its current revision, including deleted records. Follow every `next_cursor` with the same `since` value until it is null.

For a complete recovery scan, choose a `since` time before the first submission. Do not advance it from a row's `received_at`: a later deletion appears with the prior revision's receipt time. Deletion hides the record from standard Ediphai reads, and a later revision of that ID is refused. See the [sender self-test](/api/v1/self-test) for the receipt and reconciliation shapes.