Writes — coming
The write family isn't switched on in this environment yet — the endpoints below answer not_enabled until it is. Everything on this page is still the contract those endpoints keep. Production launches sandbox-first: writes arrive for sandbox organizations before live ones.
Create a draft
A write creates a draft on a connection your organization is a party to — your organization is the issuer, always. Rivet assigns the document number (INV-…); your own system's number belongs in external_refs.issuer_ref, where both the app and the search understand it. Drafts are yours alone until you issue them.
curl -X POST "https://api.rivet.network/v1/documents" \
-H "Authorization: Bearer rk_live_…" \
-H "Idempotency-Key: order-8841-invoice" \
-H "Content-Type: application/json" \
-d '{"connection_id":"6a5eed00000000000000c001","document_type":"invoice","line_items":[{"description":"Anvil, 40 lb","quantity":2,"unit_price":95,"tax_rate":0.08}],"external_refs":{"issuer_ref":"ACME-1042"}}'| Field | Type | Notes |
|---|---|---|
| connection_id* | string | The connection to create on — your organization must be a party to it. |
| document_type* | DocumentType | invoice, purchase_order, quote, … |
| line_items* | LineItemInput[] | 1–200 lines; totals are derived from them. |
| external_refs | object | Your own system's reference — Rivet assigns number itself. |
| currency | string | USD, EUR, GBP, CAD. |
| payment_terms | string | null | Terms; the missing date half is derived. |
| notes | string | null | Free text on the document. |
Tax lives on the line
Every line item carries its own tax_rate (a decimal — 0.08 is 8%; use 0 for untaxed lines). Document-level totals — subtotal, tax, the total itself — are derived from the lines, never supplied. Sending a document-level tax field gets the teaching error back:
| Field | Type | Notes |
|---|---|---|
| description* | string | What the line is. |
| quantity* | number | ≥ 0. |
| unit_price* | number | ≥ 0, in the document's currency. |
| tax_rate* | number | Decimal per line — 0.08 is 8%; 0 for untaxed lines. |
| sku | string | null | Your catalog reference. |
| discount_type | percent | fixed | null | How discount_value applies, before tax. |
| discount_value | number | The discount amount or percent. |
Edit while it's a draft
PATCH /v1/documents/{id} edits drafts only — line items replace as a whole and every total re-derives. Once issued, a document is immutable through the API; its life continues by transition, exactly as in the app. Editing a non-draft answers conflict.
Issue it
Issuing performs the type's own entry transition (submitted for an invoice, sent for a quote). From that moment the counterparty sees the same shared record in their truth: your Invoice INV-1042 is their Bill BILL-1042 — direction, label and number all flip per viewer, as described in the document model.
curl -X POST "https://api.rivet.network/v1/documents/6a5eed00000000000000d001/issue" \
-H "Authorization: Bearer rk_live_…" \
-H "Idempotency-Key: order-8841-issue"Transitions
POST /v1/documents/{id}/transition moves a document along its own state machine — only moves the machine allows for your side, the same rules the app enforces. An illegal target answers 409 conflict, and details.allowed names what is legal right now:
{
"error": {
"code": "conflict",
"message": "Cannot transition invoice from 'approved' to 'acknowledged'",
"docs_url": "https://api.rivet.network/v1/docs#errors-conflict",
"details": { "allowed": ["disputed"] }
}
}curl -X POST "https://api.rivet.network/v1/documents/6a5eed00000000000000d001/transition" \
-H "Authorization: Bearer rk_live_…" \
-H "Idempotency-Key: order-8841-ack" \
-H "Content-Type: application/json" \
-d '{"to":"acknowledged"}'Executing a payment
The payment states are part of the same machine. Moving a document to payment_scheduled — or straight to paid — executes its payment on the fiat rail, so it needs the payments:write scope on top of documents:write. Three optional body fields steer it: payment_method, source_account_id and a scheduled_for date.
- In the sandbox it is simulated: the document settles in place — its
balance_duegoes to zero — and no real money moves, so you can exercise the whole lifecycle end to end. - On a live key, the movement goes live once your organization's payment processor is connected. Until then the transition answers
capability_unavailable— the write is real; only the fiat movement is gated. A key withoutpayments:writegetsinsufficient_scope.
How your writes appear
- Every API write is attributed to the key, not to a person: document history shows
via API (rk_live_…), and the provenance record carries the key's id. originis stampedapiby the server — it can't be supplied in the body.- Every write requires an
Idempotency-Keyheader — its own page.