Delivery and retries
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:
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 andfixto correct the request before sending again. - Retryable response — HTTP 429, 503 or
retryable: true: Retry the unchanged request with backoff. HonorRetry-Afterorretry_after_mswhen 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 for the receipt and reconciliation shapes.