Writes are live here
This deployment answers the write endpoints. The scope is documents:write, granted at key creation like every other scope.
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 · delivery_confirmation · shipment_notice · bill_of_sale · settlement_statement · funding_confirmation · agreement · disbursement · repayment · gate_pass · claim |
| currency | Currency | USD · EUR · GBP · CAD |
| line_items | LineItemInput[] | |
| external_refs | object | Your own system's reference for this document — Rivet assigns `number` itself. |
| payment_terms | string | null | As people write them — a preset (`Net 30`, `Due on receipt`, `2/10 Net 30`: 2% off within 10 days, else net 30), any `Net N` / `P/D Net N`, or a code (`net_30`, `2_10_net_30`). Stored as the code and read back with `payment_terms_detail`; the due date is derived from the terms when `due_at` is not sent. Unreadable text is refused with `invalid_request` naming what is accepted. |
| issued_at | string | `YYYY-MM-DD` or an ISO-8601 timestamp. |
| due_at | string | `YYYY-MM-DD` or an ISO-8601 timestamp. |
| valid_until | string | `YYYY-MM-DD` or an ISO-8601 timestamp. |
| notes | string | null | |
| source_document_type | string | A gate pass only — the document its goods come from, on the same connection, sent and still live.bill_of_sale · invoice · purchase_order |
| source_document_id | string | A gate pass only — that document's id. |
| location | GatePassLocationInput | A gate pass only — where the goods are collected. |
| valid_from | string | A gate pass only — when collection may start (omit for when it is issued). `valid_until` is when it ends. |
| subjects | ClaimSubjectInput[] | A claim only — the documents it is about; the first is the primary. |
| category | string | A claim only — `other` (with `category_text`) unless the other party offers categories of its own. |
| category_text | string | null | A claim only — what an `other` claim is about, in your words. |
| statement | string | A claim only — what happened. Fixed once the claim is issued. |
| requested | ClaimRequested | A claim only — the remedy asked for and its amount (0 allowed). |
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 | |
| sku | string | null | |
| kind | string | item · discount · fee · tax |
| quantity | number | |
| unit_price | number | |
| tax_rate | number | Decimal rate — `0.08` is 8%. Applied per line after the line's discount. Product and service lines only. |
| discount_type | string | null | percent · fixed · null |
| discount_value | number | |
| rate_type | string | Adjustment lines — `percent` (`rate_value` is a percent, `2` is 2%, at most 100) or `flat` (an amount).percent · flat |
| rate_value | number | |
| applies_to | string | What the rate is taken of — the document's net, or one product line named by `applies_to_line`.subtotal · line |
| applies_to_line | integer | The 0-based index, in this array, of a product or service line. |
| taxable | boolean | Fee lines — true puts the fee in the base a tax line is taken of (a fee before tax). |
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.