API guides

Documents

One shared record, two sides — and how to read them.

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.

This is the page to read before you map anything. Almost every confused integration starts here: a document in Rivet is one record shared by two organizations, and a handful of its fields are answered from the point of view of whoever asked.

One record, two sides

When your customer issues you an invoice, there is not an invoice in their account and a copy of it in yours. There is one document on the connection between you. They issued it, so they see an Invoice numbered INV-0042. You received it, so you see a Bill numbered BILL-0042. Same record, same total, same id — different names, because that is what each side calls it.

Read with the receiver's key
From the published contract
{
  "id": "6a5eed00000000000000d001",
  "document_type": "invoice",
  "direction": "received",
  "label": "Bill",
  "number": "BILL-0042",
  "status": "due",
  "display_status": "Due",
  "phase": "approved",
  "connection_id": "6a5eed00000000000000c001",
  "issuer_org_id": "6a5eed0000000000000000b1",
  "receiver_org_id": "6a5eed0000000000000000a1",
  "currency": "USD",
  "total": 1250,
  "amount_paid": 0,
  "balance_due": 1250,
  "issued_at": "2026-08-01T00:00:00.000Z",
  "due_at": "2026-08-31T00:00:00.000Z"
}
Read with the issuer's key
Derived — not a second live read
{
  "id": "6a5eed00000000000000d001",
  "document_type": "invoice",
  "direction": "issued",
  "label": "Invoice",
  "number": "INV-0042",
  "status": "due",
  "display_status": "Due",
  "phase": "approved",
  "connection_id": "6a5eed00000000000000c001",
  "issuer_org_id": "6a5eed0000000000000000b1",
  "receiver_org_id": "6a5eed0000000000000000a1",
  "currency": "USD",
  "total": 1250,
  "amount_paid": 0,
  "balance_due": 1250,
  "issued_at": "2026-08-01T00:00:00.000Z",
  "due_at": "2026-08-31T00:00:00.000Z"
}

The two bodies differ in exactly direction, label, number. Everything else — the id, the connection, the totals, the dates — is byte-for-byte the same. The right-hand body is derived from the left by flipping only the fields the contract itself calls viewer-relative; it is not a second live read, and it is labelled that way.

Which fields are the viewer’s

The contract names them, and this list is read from it: direction, label, number, display_status, linked_system_number. Treat every other field as shared.

Document — the viewer-relative fields
FieldTypeNotes
direction*stringissued — this organization created/sent it. received — the counterparty did.issued · received
label*stringThe name the app shows this organization (Invoice to the issuer, Bill to the receiver).
number*string | nullThe document number as this organization sees it (INV-0001 to the issuer, BILL-0001 to the receiver).
display_status*stringThe status in this organization's own vocabulary (its connected system's, if linked).
linked_system_number*string | nullThis organization's own connected-system number for the document, if synced.
If you are storing documents on your side, key them on id — never on number. The id is the record. The number is a name one organization uses for it, and the other organization has a different one for the same thing.

Direction is the pivot

  • direction: "issued" — your organization created and sent it. Your receivables live here.
  • direction: "received" — the counterparty sent it to you. Your payables live here.

The same distinction drives the payments rollups: receivable is built from documents you issued, payable from documents you received. If a number looks inverted, check direction before you check arithmetic.

The party rule

You can read documents on connections your organization is a party to. That is the whole boundary. Ask for a document id on someone else’s connection and you get not_found — the same answer as an id that never existed.

Get one document
From the published contract
curl "https://api.rivet.network/v1/documents/6a5eed00000000000000d001" \
  -H "Authorization: Bearer rk_live_…"
The 404 is deliberate, and it is not an error in your integration. A forbidden would confirm the document exists, which would leak the fact that two other organizations trade with each other. Nothing about another organization’s documents is ever acknowledged — including their existence.

Listing documents

One list endpoint spans every connection you are a party to, newest first — the detail read above is the same record with its line items and the documents it was derived from.

Filtered list
From the published contract
curl "https://api.rivet.network/v1/documents?type=invoice&limit=2" \
  -H "Authorization: Bearer rk_live_…"
  • connection — one relationship at a time. The usual shape for a per-customer sync.
  • type — invoice, purchase order, quote and the rest.
  • status — statuses are per type, so pair it with type for a filter that means something.
  • since / until — a window on the document date.
Remember which side you are on: a filter for type=invoice returns invoices you issued and the ones you received, which you call bills. Narrow with direction in your own code after reading — the two sides are above.

Keeping a copy in sync? updated_since and the cross-type phase filter live on their own page — see Filtering & polling.