API guides

Writing documents

Draft → issue → their Bill. The same shared record, now writable.

The API isn’t switched on in this environment yet. Everything below is accurate; the examples come from the published contract rather than a live read.

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.

Create a draft invoice
From the published contract
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"}}'
CreateDocumentRequest
FieldTypeNotes
connection_id*stringThe connection to create on — your organization must be a party to it.
document_type*DocumentTypeinvoice, purchase_order, quote, …
line_items*LineItemInput[]1–200 lines; totals are derived from them.
external_refsobjectYour own system's reference — Rivet assigns number itself.
currencystringUSD, EUR, GBP, CAD.
payment_termsstring | nullTerms; the missing date half is derived.
notesstring | nullFree 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:

“Tax is per line in Rivet — set tax_rate on each line item (0 for untaxed lines); a document-level tax_total is derived from the lines and cannot be supplied”
LineItemInput
FieldTypeNotes
description*stringWhat the line is.
quantity*number≥ 0.
unit_price*number≥ 0, in the document's currency.
tax_rate*numberDecimal per line — 0.08 is 8%; 0 for untaxed lines.
skustring | nullYour catalog reference.
discount_typepercent | fixed | nullHow discount_value applies, before tax.
discount_valuenumberThe 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.

Issue the draft
From the published contract
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:

An illegal transition, answered
From the published contract
{
  "error": {
    "code": "conflict",
    "message": "Cannot transition invoice from 'approved' to 'acknowledged'",
    "docs_url": "https://api.rivet.network/v1/docs#errors-conflict",
    "details": { "allowed": ["disputed"] }
  }
}
A legal transition
From the published contract
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_due goes 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 without payments:write gets insufficient_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.
  • origin is stamped api by the server — it can't be supplied in the body.
  • Every write requires an Idempotency-Key header — its own page.