API guides

Writing documents

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

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.

Create a draft invoice
From the live spec
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 · delivery_confirmation · shipment_notice · bill_of_sale · settlement_statement · funding_confirmation · agreement · disbursement · repayment · gate_pass · claim
currencyCurrencyUSD · EUR · GBP · CAD
line_itemsLineItemInput[]
external_refsobjectYour own system's reference for this document — Rivet assigns `number` itself.
payment_termsstring | nullAs 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_atstring`YYYY-MM-DD` or an ISO-8601 timestamp.
due_atstring`YYYY-MM-DD` or an ISO-8601 timestamp.
valid_untilstring`YYYY-MM-DD` or an ISO-8601 timestamp.
notesstring | null
source_document_typestringA 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_idstringA gate pass only — that document's id.
locationGatePassLocationInputA gate pass only — where the goods are collected.
valid_fromstringA gate pass only — when collection may start (omit for when it is issued). `valid_until` is when it ends.
subjectsClaimSubjectInput[]A claim only — the documents it is about; the first is the primary.
categorystringA claim only — `other` (with `category_text`) unless the other party offers categories of its own.
category_textstring | nullA claim only — what an `other` claim is about, in your words.
statementstringA claim only — what happened. Fixed once the claim is issued.
requestedClaimRequestedA 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:

“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*string
skustring | null
kindstringitem · discount · fee · tax
quantitynumber
unit_pricenumber
tax_ratenumberDecimal rate — `0.08` is 8%. Applied per line after the line's discount. Product and service lines only.
discount_typestring | nullpercent · fixed · null
discount_valuenumber
rate_typestringAdjustment lines — `percent` (`rate_value` is a percent, `2` is 2%, at most 100) or `flat` (an amount).percent · flat
rate_valuenumber
applies_tostringWhat the rate is taken of — the document's net, or one product line named by `applies_to_line`.subtotal · line
applies_to_lineintegerThe 0-based index, in this array, of a product or service line.
taxablebooleanFee 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.

Issue the draft
From the live spec
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 live spec
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.