# Rivet MCP tools

> Every tool `@rivet-network/mcp` serves — 61 of them, one per operation in the Rivet API contract (v1.21.0), each named by its `operationId`. Generated from the contract by `mcp/build-tool-docs.mjs`: to change a line here, change the spec.

How to read an entry:

- **Effect.** `read` changes nothing. `write` changes the shared record. `money` can move money (simulated in a sandbox) — call it only when a person asked for that payment. `admin` changes how the organization is wired: keys, webhooks, the sandbox.
- **Scope.** The key scope the call needs. A scope never exceeds what the organization itself has; the two are intersected on every call, and the gap answers `capability_unavailable`.
- **Family.** The switch the operation sits behind. `getEnablement` says whether it is on in this deployment; a family that is off answers `not_enabled`.
- **Auth.** `key`: a Bearer API key. `key or admin session`: the same, or a signed-in admin. `none`: no credential — for pay by agent, the pay reference is the capability.
- **Inputs.** Path and query parameters are top-level arguments. Fields shown as `body.…` go inside the `body` argument. The five document writes and `createShareItem` also take `idempotency_key`: build it from the action and the attempt, and send the same key on a retry. Other writes take no key — after an unclear failure, read the state before trying again.

In this version: 29 read · 15 write · 3 money · 14 admin.

## At a glance

| Tool | Effect | Scope | What it does |
|---|---|---|---|
| [`getEnablement`](#getenablement) | read | — | Which families this deployment can serve |
| [`listDocuments`](#listdocuments) | read | `documents:read` | List documents |
| [`createDocument`](#createdocument) | write | `documents:write` | Create a document (draft) |
| [`getDocument`](#getdocument) | read | `documents:read` | Get a document |
| [`updateDocument`](#updatedocument) | write | `documents:write` | Edit a draft |
| [`listDocumentAttachments`](#listdocumentattachments) | read | `documents:read` | List a document's attachments |
| [`issueDocument`](#issuedocument) | write | `documents:write` | Issue a draft |
| [`transitionDocument`](#transitiondocument) | money | `documents:write` | Transition a document |
| [`payDocument`](#paydocument) | money | `documents:write` | Pay a document |
| [`getPaymentsContext`](#getpaymentscontext) | read | `payments:read` | Payable and receivable rollups |
| [`listPaymentMethods`](#listpaymentmethods) | read | `payments:read` | List payment methods |
| [`getPayChallenge`](#getpaychallenge) | read | — | Read an invoice's x402 payment challenge |
| [`payWithX402`](#paywithx402) | money | — | Settle an invoice with a signed x402 authorization |
| [`listTreasuryAccounts`](#listtreasuryaccounts) | read | `treasury:read` | List treasury accounts |
| [`listTreasuryBalances`](#listtreasurybalances) | read | `treasury:read` | List treasury balances |
| [`listWebhookEndpoints`](#listwebhookendpoints) | read | `keys:manage` | List webhook endpoints |
| [`createWebhookEndpoint`](#createwebhookendpoint) | admin | `keys:manage` | Register a webhook endpoint |
| [`getWebhookEndpoint`](#getwebhookendpoint) | read | `keys:manage` | Get a webhook endpoint |
| [`updateWebhookEndpoint`](#updatewebhookendpoint) | admin | `keys:manage` | Update a webhook endpoint |
| [`deleteWebhookEndpoint`](#deletewebhookendpoint) | admin | `keys:manage` | Delete a webhook endpoint |
| [`rotateWebhookSecret`](#rotatewebhooksecret) | admin | `keys:manage` | Rotate the signing secret |
| [`pingWebhookEndpoint`](#pingwebhookendpoint) | admin | `keys:manage` | Send a test event |
| [`listWebhookDeliveries`](#listwebhookdeliveries) | read | `keys:manage` | Recent deliveries |
| [`redeliverWebhook`](#redeliverwebhook) | admin | `keys:manage` | Redeliver |
| [`getDocumentHistory`](#getdocumenthistory) | read | `provenance:read` | A document's verified history |
| [`getDocumentLineage`](#getdocumentlineage) | read | `provenance:read` | A document's lineage |
| [`exportDocumentProvenance`](#exportdocumentprovenance) | read | `provenance:read` | Export the verifiable proof bundle |
| [`getDocumentAttestation`](#getdocumentattestation) | read | `provenance:read` | The signed attestation (Rivet Finance) |
| [`financeOptIn`](#financeoptin) | write | `finance:write` | Opt the organization in or out of the marketplace |
| [`listFinanceEligible`](#listfinanceeligible) | read | `finance:read` | Your invoices, with their listing eligibility |
| [`browseFinanceListings`](#browsefinancelistings) | read | `finance:read` | Browse open listings (pseudonymous) |
| [`createFinanceListing`](#createfinancelisting) | write | `finance:write` | List an eligible invoice |
| [`withdrawFinanceListing`](#withdrawfinancelisting) | write | `finance:write` | Withdraw a listing |
| [`listFinanceListingInterests`](#listfinancelistinginterests) | read | `finance:read` | The interests on your listing (funders pseudonymous) |
| [`expressFinanceInterest`](#expressfinanceinterest) | write | `finance:write` | Express interest in a listing |
| [`withdrawFinanceInterest`](#withdrawfinanceinterest) | write | `finance:write` | Withdraw your interest |
| [`consentFinanceInterest`](#consentfinanceinterest) | write | `finance:write` | Consent to disclosure (the lister's act) |
| [`getFinanceDisclosure`](#getfinancedisclosure) | read | `finance:read` | The disclosed introduction (the funder's read) |
| [`getFinanceSetup`](#getfinancesetup) | read | `finance:read` | Your finance setup |
| [`setFinanceRails`](#setfinancerails) | write | `finance:write` | Name your accounts on a rail |
| [`listFinanceOffers`](#listfinanceoffers) | read | `finance:read` | Offers, both directions |
| [`createFinanceOffer`](#createfinanceoffer) | write | `finance:write` | Offer on a listing |
| [`getFinanceOffer`](#getfinanceoffer) | read | `finance:read` | One offer |
| [`withdrawFinanceOffer`](#withdrawfinanceoffer) | write | `finance:write` | Withdraw your pending offer |
| [`acceptFinanceOffer`](#acceptfinanceoffer) | write | `finance:write` | Accept an offer (creates the agreement) |
| [`listFinanceAgreements`](#listfinanceagreements) | read | `finance:read` | Your agreements, both sides |
| [`getFinanceAgreement`](#getfinanceagreement) | read | `finance:read` | One agreement |
| [`withdrawFinanceAgreement`](#withdrawfinanceagreement) | write | `finance:write` | Withdraw before funding (either party) |
| [`getFinanceRepayment`](#getfinancerepayment) | read | `finance:read` | Repayment status |
| [`getSandbox`](#getsandbox) | read | `keys:manage` | Your sandbox's state |
| [`provisionSandbox`](#provisionsandbox) | admin | `keys:manage` | Create your sandbox |
| [`deleteSandbox`](#deletesandbox) | admin | `keys:manage` | Tear your sandbox down |
| [`resetSandbox`](#resetsandbox) | admin | `keys:manage` | Reset your sandbox |
| [`setSandboxMoney`](#setsandboxmoney) | admin | `keys:manage` | Turn simulated Rivet Money on or off in your sandbox |
| [`advanceSandboxCounterparty`](#advancesandboxcounterparty) | admin | `keys:manage` | The counterparty acts |
| [`createShareItem`](#createshareitem) | write | `share:write` | Add an HTML page or a link to the share drive |
| [`getShareItem`](#getshareitem) | read | `share:write` | Get a share item |
| [`listApiKeys`](#listapikeys) | read | `keys:manage` | List the organization's API keys |
| [`createApiKey`](#createapikey) | admin | `keys:manage` | Create an API key |
| [`revokeApiKey`](#revokeapikey) | admin | `keys:manage` | Revoke an API key |
| [`rotateApiKey`](#rotateapikey) | admin | `keys:manage` | Rotate an API key with a 24-hour grace |

## Meta

The specification itself.

### getEnablement

**Which families this deployment can serve** · `GET /v1/enablement` · effect **read** · no scope · auth: none

Unauthenticated. The single answer to "is this family usable here" —
so a console or a docs page never keeps its own copy of the switch.
`available` is the same pair of conditions the family's own gate
applies, so it cannot disagree with what a real call would do.
`reason` appears only when unavailable: `not_enabled` (deliberately
dark) or `service_unavailable` (switched on, backing service not
configured or unreachable).

_No inputs._

## Documents

Invoices/bills, purchase orders, quotes and the other document types an
organization exchanges over its connections. Fields are direction-aware:
`direction` says whether this organization issued or received the
document and `label` is the name the app shows this organization for it
(an invoice you issued is an `Invoice`; the same document is a `Bill` to
the organization that received it).

### listDocuments

**List documents** · `GET /v1/documents` · effect **read** · scope `documents:read` · family `documents` · auth: key

Every document the organization is a party to, across all of its
connections, newest first (`created_at`). Filter by connection, type,
status and a date window on the document date (`issued_at`). The
counterparty's drafts are never among them: a draft is visible to its
issuer only until it is issued.

Paginate with `cursor`: pass the previous page's `next_cursor` until
it is `null`.

| Input | Type | Required | Notes |
|---|---|---|---|
| `connection` | id (24 hex) |  | Only documents on this connection (the organization must be a party to it). |
| `type` | enum: `invoice`, `purchase_order`, `quote`, `delivery_confirmation`, `shipment_notice`, `bill_of_sale`, `settlement_statement`, `funding_confirmation`, `agreement`, `disbursement`, … 13 values |  | Only documents of this type. |
| `status` | enum: `draft`, `submitted`, `acknowledged`, `approved`, `payment_scheduled`, `paid`, `due`, `overdue`, `disputed`, `resolved`, … 39 values |  | Only documents in this status. Statuses are per type — combine with `type` for a meaningful filter. |
| `since` | string |  | Inclusive start of the window — an ISO date (`YYYY-MM-DD`) or timestamp. · ≤ 40 chars |
| `until` | string |  | Inclusive end of the window — an ISO date (through the end of that UTC day) or timestamp. · ≤ 40 chars |
| `updated_since` | string |  | Only documents changed at or after this instant (`updated_at`) — the polling filter; sort order is unchanged. · ≤ 40 chars |
| `phase` | enum: `draft`, `awaiting_counterparty`, `in_review`, `approved`, `scheduled`, `overdue`, `exception`, `settled`, `closed` |  | Only documents in this cross-type lifecycle phase — one vocabulary across all document types. |
| `cursor` | string |  | Opaque page cursor from a previous response's `next_cursor`. · ≤ 256 chars |
| `limit` | integer |  | Page size (1–100, default 20). · 1–100 · default 20 |

### createDocument

**Create a document (draft)** · `POST /v1/documents` · effect **write** · scope `documents:write` · family `writes` (behind `API_WRITES_ENABLED`) · auth: key

Creates a draft on a connection your organization is a party to; your
organization is the issuer. The document number is assigned by Rivet
(`INV-…`) — put your own system's number in
`external_refs.issuer_ref`. **Tax is per line**: set `tax_rate` on each
line item (0 for untaxed lines); totals are derived and a
document-level tax amount is rejected with a teaching error. The draft
is visible to your side only until you issue it.

**A gate pass** (`gate_pass`) is built from a bill of sale, invoice or
purchase order on the same connection (`source_document_type`,
`source_document_id`): its goods are the source's product lines with
their identifiers (VIN, stock and lot numbers, mileage, color). A gate
pass never carries a price — it takes no `line_items` — and is issued
with a pickup `location` and a `valid_until` still ahead.

**A claim** (`claim`) asks the other party for a remedy: the documents
it is about (`subjects`), a `category` (`other` with `category_text`),
a `statement` and what it asks for (`requested`). It takes no
`line_items`, and its decision is the other party's — never part of
creating it.

| Input | Type | Required | Notes |
|---|---|---|---|
| `body.connection_id` | id (24 hex) | yes | The connection to create on — your organization must be a party to it. |
| `body.document_type` | enum: `invoice`, `purchase_order`, `quote`, `delivery_confirmation`, `shipment_notice`, `bill_of_sale`, `settlement_statement`, `funding_confirmation`, `agreement`, `disbursement`, … 13 values | yes |  |
| `body.currency` | enum: `USD`, `EUR`, `GBP`, `CAD` |  |  |
| `body.line_items` | object[] |  | One line of the document. A product or service line (`kind` omitted or `item`) needs `quantity`, `unit_price` and `tax_rate` — tax lives per line (0 for untaxed); a document-level tax figure is derived, never supplied. An adjustment line (`kind` `discount`, `fee` or `tax`) needs `rate_type` and `rate_value` instead and never a quantity or unit price. Discounts come off in order, before tax; a fee is added after tax unless `taxable`; a percent tax line is taken of the lines that carry no `tax_rate` of their own plus the taxable fees. A line that breaks these rules is refused with `invalid_request` naming it. · 1–200 items |
| `body.line_items[].description` | string |  | 1–1000 chars |
| `body.line_items[].sku` | string \| null |  | ≤ 100 chars |
| `body.line_items[].kind` | enum: `item`, `discount`, `fee`, `tax` |  | default "item" |
| `body.line_items[].quantity` | number |  | ≥ 0 |
| `body.line_items[].unit_price` | number |  | ≥ 0 |
| `body.line_items[].tax_rate` | number |  | Decimal rate — `0.08` is 8%. Applied per line after the line's discount. Product and service lines only. · 0–1 |
| `body.line_items[].discount_type` | enum: `percent`, `fixed` · or null |  |  |
| `body.line_items[].discount_value` | number |  | ≥ 0 |
| `body.line_items[].rate_type` | enum: `percent`, `flat` |  | Adjustment lines — `percent` (`rate_value` is a percent, `2` is 2%, at most 100) or `flat` (an amount). |
| `body.line_items[].rate_value` | number |  | ≥ 0 |
| `body.line_items[].applies_to` | enum: `subtotal`, `line` |  | What the rate is taken of — the document's net, or one product line named by `applies_to_line`. · default "subtotal" |
| `body.line_items[].applies_to_line` | integer |  | The 0-based index, in this array, of a product or service line. · ≥ 0 |
| `body.line_items[].taxable` | boolean |  | Fee lines — true puts the fee in the base a tax line is taken of (a fee before tax). · default false |
| `body.external_refs` | object |  | Your own system's reference for this document — Rivet assigns `number` itself. |
| `body.external_refs.issuer_ref` | string \| null |  | ≤ 100 chars |
| `body.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. |
| `body.issued_at` | string |  | `YYYY-MM-DD` or an ISO-8601 timestamp. · ≤ 40 chars |
| `body.due_at` | string |  | `YYYY-MM-DD` or an ISO-8601 timestamp. · ≤ 40 chars |
| `body.valid_until` | string |  | `YYYY-MM-DD` or an ISO-8601 timestamp. · ≤ 40 chars |
| `body.notes` | string \| null |  | ≤ 4000 chars |
| `body.source_document_type` | enum: `bill_of_sale`, `invoice`, `purchase_order` |  | A gate pass only — the document its goods come from, on the same connection, sent and still live. |
| `body.source_document_id` | id (24 hex) |  | A gate pass only — that document's id. |
| `body.location` | object |  | A gate pass only — where the goods are collected. |
| `body.location.address` | object \| null |  |  |
| `body.location.note` | string \| null |  | ≤ 500 chars |
| `body.valid_from` | string |  | A gate pass only — when collection may start (omit for when it is issued). `valid_until` is when it ends. · ≤ 40 chars |
| `body.subjects` | any \| any[] |  | A claim only — the documents it is about; the first is the primary. · 1–50 items |
| `body.subjects[].document_type` | enum: `bill_of_sale`, `invoice`, `gate_pass`, `delivery_confirmation`, `purchase_order`, `claim` |  |  |
| `body.subjects[].document_id` | id (24 hex) |  | A Rivet resource id. |
| `body.subjects[].document_number` | string |  | 1–100 chars |
| `body.subjects[].line_indexes` | integer[] |  | The 0-based lines of that document the claim is about. · ≤ 200 items |
| `body.category` | string |  | A claim only — `other` (with `category_text`) unless the other party offers categories of its own. · 1–100 chars |
| `body.category_text` | string \| null |  | A claim only — what an `other` claim is about, in your words. · ≤ 200 chars |
| `body.statement` | string |  | A claim only — what happened. Fixed once the claim is issued. · 1–10000 chars |
| `body.requested` | object |  | A claim only — the remedy asked for and its amount (0 allowed). |
| `body.requested.remedy` | enum: `adjust`, `cancel`, `repair`, `other` |  |  |
| `body.requested.amount` | number |  | ≥ 0 |
| `idempotency_key` | string |  | A stable key for this logical action (8–128 chars), e.g. invoice-ACME-1042. Send the SAME key when you retry after a timeout — a new key is a new write. Generated per call when omitted. |

### getDocument

**Get a document** · `GET /v1/documents/{documentId}` · effect **read** · scope `documents:read` · family `documents` · auth: key

One document with its line items and related (parent) documents. A
document on a connection the organization is not a party to answers
`not_found` — nothing about another organization's documents is ever
acknowledged. The counterparty's draft answers the same `not_found`: a
draft is visible to its issuer only until it is issued.

| Input | Type | Required | Notes |
|---|---|---|---|
| `documentId` | id (24 hex) | yes | A Rivet resource id. |

### updateDocument

**Edit a draft** · `PATCH /v1/documents/{documentId}` · effect **write** · scope `documents:write` · family `writes` (behind `API_WRITES_ENABLED`) · auth: key

Drafts only — an issued document is immutable through the API (its
lifecycle continues by transition). Line items are replaced as a whole
when supplied; totals are re-derived. A non-draft answers `conflict`.

| Input | Type | Required | Notes |
|---|---|---|---|
| `documentId` | id (24 hex) | yes | A Rivet resource id. |
| `body.currency` | enum: `USD`, `EUR`, `GBP`, `CAD` |  |  |
| `body.line_items` | object[] |  | One line of the document. A product or service line (`kind` omitted or `item`) needs `quantity`, `unit_price` and `tax_rate` — tax lives per line (0 for untaxed); a document-level tax figure is derived, never supplied. An adjustment line (`kind` `discount`, `fee` or `tax`) needs `rate_type` and `rate_value` instead and never a quantity or unit price. Discounts come off in order, before tax; a fee is added after tax unless `taxable`; a percent tax line is taken of the lines that carry no `tax_rate` of their own plus the taxable fees. A line that breaks these rules is refused with `invalid_request` naming it. · 1–200 items |
| `body.line_items[].description` | string |  | 1–1000 chars |
| `body.line_items[].sku` | string \| null |  | ≤ 100 chars |
| `body.line_items[].kind` | enum: `item`, `discount`, `fee`, `tax` |  | default "item" |
| `body.line_items[].quantity` | number |  | ≥ 0 |
| `body.line_items[].unit_price` | number |  | ≥ 0 |
| `body.line_items[].tax_rate` | number |  | Decimal rate — `0.08` is 8%. Applied per line after the line's discount. Product and service lines only. · 0–1 |
| `body.line_items[].discount_type` | enum: `percent`, `fixed` · or null |  |  |
| `body.line_items[].discount_value` | number |  | ≥ 0 |
| `body.line_items[].rate_type` | enum: `percent`, `flat` |  | Adjustment lines — `percent` (`rate_value` is a percent, `2` is 2%, at most 100) or `flat` (an amount). |
| `body.line_items[].rate_value` | number |  | ≥ 0 |
| `body.line_items[].applies_to` | enum: `subtotal`, `line` |  | What the rate is taken of — the document's net, or one product line named by `applies_to_line`. · default "subtotal" |
| `body.line_items[].applies_to_line` | integer |  | The 0-based index, in this array, of a product or service line. · ≥ 0 |
| `body.line_items[].taxable` | boolean |  | Fee lines — true puts the fee in the base a tax line is taken of (a fee before tax). · default false |
| `body.external_refs` | object |  |  |
| `body.external_refs.issuer_ref` | string \| null |  | ≤ 100 chars |
| `body.payment_terms` | string \| null |  | As on create — a preset, any `Net N` / `P/D Net N`, or a code; stored as the code. Changing the terms re-derives the due date. |
| `body.issued_at` | string |  | `YYYY-MM-DD` or an ISO-8601 timestamp. · ≤ 40 chars |
| `body.due_at` | string |  | `YYYY-MM-DD` or an ISO-8601 timestamp. · ≤ 40 chars |
| `body.valid_until` | string |  | `YYYY-MM-DD` or an ISO-8601 timestamp. · ≤ 40 chars |
| `body.notes` | string \| null |  | ≤ 4000 chars |
| `idempotency_key` | string |  | A stable key for this logical action (8–128 chars), e.g. invoice-ACME-1042. Send the SAME key when you retry after a timeout — a new key is a new write. Generated per call when omitted. |

### listDocumentAttachments

**List a document's attachments** · `GET /v1/documents/{documentId}/attachments` · effect **read** · scope `documents:read` · family `documents` · auth: key

The files attached to a document, each with a time-limited
`download_url` (a signed link — fetch it promptly, request the list
again when it expires). A document on a connection your organization is
not a party to answers `not_found`.

| Input | Type | Required | Notes |
|---|---|---|---|
| `documentId` | id (24 hex) | yes | A Rivet resource id. |

### issueDocument

**Issue a draft** · `POST /v1/documents/{documentId}/issue` · effect **write** · scope `documents:write` · family `writes` (behind `API_WRITES_ENABLED`) · auth: key

Sends the draft to the counterparty — the per-type entry transition
(`submitted` for an invoice, `sent` for a quote, …). From here the
counterparty sees it as their own document (your Invoice is their
Bill). Issuing a non-draft answers `conflict` with the legal set.

| Input | Type | Required | Notes |
|---|---|---|---|
| `documentId` | id (24 hex) | yes | A Rivet resource id. |
| `idempotency_key` | string |  | A stable key for this logical action (8–128 chars), e.g. invoice-ACME-1042. Send the SAME key when you retry after a timeout — a new key is a new write. Generated per call when omitted. |

### transitionDocument

**Transition a document** · `POST /v1/documents/{documentId}/transition` · effect **money** · scope `documents:write` · family `writes` (behind `API_WRITES_ENABLED`) · auth: key

Moves the document along its own state machine — only transitions the
machine allows for your side, exactly as in the app. An illegal target
answers `conflict` and `details.allowed` names what is legal right now.

The payment transitions (`payment_scheduled`, `paid` — the fiat rail)
additionally require the `payments:write` scope. They are always
available in a sandbox (simulated — no money moves); for a live
organization they open once fiat payments are enabled (until then they
answer `capability_unavailable`). Pass `payment_method`,
`source_account_id` and (optionally) `scheduled_for` when scheduling.

| Input | Type | Required | Notes |
|---|---|---|---|
| `documentId` | id (24 hex) | yes | A Rivet resource id. |
| `body.to` | enum: `draft`, `submitted`, `acknowledged`, `approved`, `payment_scheduled`, `paid`, `due`, `overdue`, `disputed`, `resolved`, … 39 values | yes | Per-type lifecycle status (lowercase). The set is the union across types. |
| `body.payment_method` | string \| null |  | For a payment transition (`payment_scheduled`) — the rail to release on. Ignored for non-payment transitions. |
| `body.source_account_id` | string \| null |  | For a payment transition — the account the release draws from. |
| `body.scheduled_for` | string \| null |  | For `payment_scheduled` — a future date to release on; omit to release now. |
| `idempotency_key` | string |  | A stable key for this logical action (8–128 chars), e.g. invoice-ACME-1042. Send the SAME key when you retry after a timeout — a new key is a new write. Generated per call when omitted. |

### payDocument

**Pay a document** · `POST /v1/documents/{documentId}/pay` · effect **money** · scope `documents:write` · family `writes` (behind `API_WRITES_ENABLED`) · auth: key

Pays this document's full balance due. Without `payment_method: usdc`
it schedules the payment on the fiat rail — the same rail, gates and
simulation as `transition` to `payment_scheduled`, as one call. Needs
`payments:write`; in a sandbox the payment is SIMULATED (the document
settles, no money moves); a live organization's fiat rail opens once
fiat payments are enabled (`capability_unavailable` until then).

Always the full balance — partial amounts are not accepted. Keys minted
with `payment_limits` are enforced here before anything is scheduled or
challenged: exceeding either limit answers `payment_limit_exceeded`
(403) naming which, and a refused payment never consumes daily volume.

Idempotency: replaying the same
`Idempotency-Key` returns the ORIGINAL response (`Idempotent-Replay:
true`), never a second payment, even after the payment has settled or
failed. Read the document (or the `payment.settled` / `payment.failed`
webhooks) for the outcome: the document IS the payment's status.

**`payment_method: usdc` — pay by agent, authenticated.** The payer
organization's key takes the road a stranger's agent takes through
`GET/POST /v1/pay/{payRef}`: with no signature the answer is the x402
v2 challenge (`402` + `PAYMENT-REQUIRED`); with the signed
`PaymentPayload` as the `PAYMENT-SIGNATURE` header or as the body's
`payment` (for a caller that cannot set headers — every MCP tool; the
header wins) the invoice settles payer → payee on-chain and the answer
is `200` + `PAYMENT-RESPONSE` with a `PaymentReceipt`. `payment` beside
any other `payment_method` is `invalid_request`. This road's gate is
its own: the `payments:write` scope on the key, and pay by agent on in
this deployment (`families.pay` in `getEnablement`; `503 not_enabled`
otherwise) — never the fiat switch, because the USDC moves from the
payer's wallet to the payee's address, not through Rivet's processor.
A live organization pays in USDC with the fiat rail still dark.

| Input | Type | Required | Notes |
|---|---|---|---|
| `documentId` | id (24 hex) | yes | A Rivet resource id. |
| `body.payment_method` | string \| null |  | The rail/method for the release; omit for the default. `usdc` is pay by agent: the x402 challenge / settle (see the Pay by agent tag) instead of a fiat release. |
| `body.source_account_id` | string \| null |  | The funding account the release draws from; omit for the default. |
| `body.payment` | object |  | The signed x402 `PaymentPayload` (see the Pay by agent tag) — the body-shaped twin of the `PAYMENT-SIGNATURE` header, for callers that cannot set headers (tool runners, the MCP server). Only beside `payment_method: usdc`; anywhere else it is refused as `invalid_request`. The header wins when both are sent. Omit it to receive the challenge. |
| `body.payment.x402Version` | enum: `2` |  |  |
| `body.payment.resource` | object |  |  |
| `body.payment.accepted` | object |  | One way to pay (x402 v2 `accepts[]` entry). v1 offers exactly one — `exact`, USDC on Base (or Base Sepolia for a sandbox payee). |
| `body.payment.payload` | object |  |  |
| `idempotency_key` | string |  | A stable key for this logical action (8–128 chars), e.g. invoice-ACME-1042. Send the SAME key when you retry after a timeout — a new key is a new write. Generated per call when omitted. |

## Payments

Payable/receivable rollups and the organization's payment methods (bank name + last4 only).

### getPaymentsContext

**Payable and receivable rollups** · `GET /v1/payments/context` · effect **read** · scope `payments:read` · family `payments` · auth: key

Invoice counts and totals by status, from both sides: `payable` is
what this organization owes (documents it received — its bills),
`receivable` is what it is owed (documents it issued — its invoices).
`outstanding` sums every unpaid status.

The optional window applies per status to the date that matters for
it: the document date for open statuses, the due date for `due` /
`overdue`, the scheduled date for `payment_scheduled`, the paid date
for `paid`.

| Input | Type | Required | Notes |
|---|---|---|---|
| `since` | string |  | Inclusive start of the window — an ISO date (`YYYY-MM-DD`) or timestamp. · ≤ 40 chars |
| `until` | string |  | Inclusive end of the window — an ISO date (through the end of that UTC day) or timestamp. · ≤ 40 chars |

### listPaymentMethods

**List payment methods** · `GET /v1/payment-methods` · effect **read** · scope `payments:read` · family `payments` · auth: key

The organization's active payment methods. Only the bank name and the
last four digits are ever returned — never account, routing or
provider credentials.

_No inputs._

## Pay by agent

Pay a Rivet invoice in USDC on Base with **x402** — as an outside agent holding
the invoice's pay reference (no account, no key), or as the payer organization's
own key (`POST /v1/documents/{id}/pay` with `payment_method: usdc`). `GET` answers
the x402 **v2** challenge (`402` + `PAYMENT-REQUIRED`, a base64 `PaymentRequired`);
the wallet signs an EIP-3009 `TransferWithAuthorization` for exactly the challenged
amount; `POST` with `PAYMENT-SIGNATURE` (a base64 `PaymentPayload`) settles it through
Coinbase's facilitator **payer → payee on-chain** — Rivet never holds the funds —
and answers `200` + `PAYMENT-RESPONSE` once the invoice reads paid. The payee opts
in per organization and receives at an address Rivet Money has attested. Sandbox
organizations (and every deployment with `X402_TESTNET_ONLY`) challenge on Base
Sepolia (`eip155:84532`) against the public testnet facilitator.

### getPayChallenge

**Read an invoice's x402 payment challenge** · `GET /v1/pay/{payRef}` · effect **read** · no scope · family `pay` (behind `X402_PAY_ENABLED`) · auth: none

**Unauthenticated.** The pay reference — on the payee's PDF ("Pay by agent"),
on the payer's `GET /v1/documents/{id}` and on the payer's webhook deliveries
(`data.document.pay_url`, from the first event after the send) — is the
capability: it lets its holder pay this one
invoice and nothing else. Per-IP rate-limited (30/min by default; `429` +
`Retry-After`); behind `X402_PAY_ENABLED` (`503 not_enabled` otherwise).

If the invoice is payable the answer is **`402`** with the x402 **v2**
`PAYMENT-REQUIRED` header (base64 `PaymentRequired`: `accepts[0]` names the
`exact` scheme, the network (`eip155:8453` Base, or `eip155:84532` Base Sepolia
for a sandbox payee), the amount in **atomic USDC** (balance due × 10^6), the
USDC contract, the payee's attested `payTo`, a 300 s window and the token's
EIP-712 domain in `extra`) and a JSON body that mirrors it for humans.
A paid invoice answers `200 {status: "paid"}` — never a second challenge.
Not payable: `409 not_payable` (draft, cancelled, disputed, or a payment
already scheduled on the Rivet rail), `409 approval_required` (the payer's own
workflow holds a pending approval — an outside payment never bypasses it),
`409 financed_invoice_rivet_rail_only` (a financed invoice repays from Rivet
settlement only). An unknown or rotated reference, or a payee that opted
out, is `404` — nothing about the invoice is acknowledged.

| Input | Type | Required | Notes |
|---|---|---|---|
| `payRef` | string | yes | The invoice's pay reference — 32 random bytes, base64url (43 characters). The capability to pay this one invoice. |

### payWithX402

**Settle an invoice with a signed x402 authorization** · `POST /v1/pay/{payRef}` · effect **money** · no scope · family `pay` (behind `X402_PAY_ENABLED`) · auth: none

**Unauthenticated**, same gates as `GET`. Send the signed authorization as
the `PAYMENT-SIGNATURE` header (base64 `PaymentPayload`, x402 **v2**), or the
same object as the JSON body's `payment` (the form tool-callers such as the
MCP server use). The server re-checks the state, requires
`accepted.amount` to equal the **current** balance in atomic USDC
(`409 payment_amount_stale` otherwise — the signature is for a value that is
no longer the debt; fetch a fresh challenge), then settles through the
facilitator: Coinbase submits the EIP-3009 transfer **payer → payee**
on-chain, Rivet books the fact and the invoice reads paid. Answers `200` with
`PAYMENT-RESPONSE` (base64 `{success, transaction, network, payer}`) and a
`PaymentReceipt`. The same authorization sent twice answers `200` with the
original transaction — idempotent for the agent. A facilitator that refuses
the signature re-issues the challenge (`402`, `error` set); one that fails to
settle answers `502 x402_settle_failed` with its reason and nothing booked.
No body and no header → the challenge. The x402 **v1** wire (`X-PAYMENT`) is
refused `400` by name.

| Input | Type | Required | Notes |
|---|---|---|---|
| `payRef` | string | yes | The invoice's pay reference — 32 random bytes, base64url (43 characters). The capability to pay this one invoice. |
| `body.payment` | object |  | The decoded `PAYMENT-SIGNATURE` header (x402 v2, `exact` scheme on EVM) — the signed EIP-3009 `TransferWithAuthorization`. |
| `body.payment.x402Version` | enum: `2` |  |  |
| `body.payment.resource` | object |  |  |
| `body.payment.accepted` | object |  | One way to pay (x402 v2 `accepts[]` entry). v1 offers exactly one — `exact`, USDC on Base (or Base Sepolia for a sandbox payee). |
| `body.payment.payload` | object |  |  |

## Treasury

Treasury accounts and their ledger-derived balances. Available to
verified organizations only — a `treasury:read` scope on a key never
exceeds what the organization itself has.

### listTreasuryAccounts

**List treasury accounts** · `GET /v1/treasury/accounts` · effect **read** · scope `treasury:read` · family `treasury` (behind `API_TREASURY_ENABLED`) · auth: key

The organization's treasury (value) accounts with their ledger-derived
balances. `balance` is an exact decimal string at the asset's
`decimals` scale — never a float.

_No inputs._

### listTreasuryBalances

**List treasury balances** · `GET /v1/treasury/balances` · effect **read** · scope `treasury:read` · family `treasury` (behind `API_TREASURY_ENABLED`) · auth: key

One balance per treasury account — the same ledger-derived figure `listTreasuryAccounts` carries, without the account detail.

_No inputs._

## Webhooks

Signed event deliveries to your own endpoints — the push complement to
polling with `updated_since`. Managed like keys (admin session or a
`keys:manage` key); each endpoint has its own signing secret, shown at
creation and on rotate.

### listWebhookEndpoints

**List webhook endpoints** · `GET /v1/webhooks` · effect **read** · scope `keys:manage` · family `webhooks` (behind `API_WEBHOOKS_ENABLED`) · auth: key or admin session

Every webhook endpoint your organization has registered, newest first,
disabled ones included: each one's URL, subscribed events, `status`
(`ACTIVE` or `DISABLED`, with `disabled_reason`) and when it last
succeeded and failed. Signing secrets are never returned.

_No inputs._

### createWebhookEndpoint

**Register a webhook endpoint** · `POST /v1/webhooks` · effect **admin** · scope `keys:manage` · family `webhooks` (behind `API_WEBHOOKS_ENABLED`) · auth: key or admin session

Registers an https endpoint (up to 10 per organization). **The signing
secret is returned once**, here — store it with the same care as a key.
An empty `events` list means every event, including ones added later.

| Input | Type | Required | Notes |
|---|---|---|---|
| `body.url` | string (uri) | yes | https only (http allowed for localhost during development). · ≤ 2000 chars |
| `body.events` | enum: `document.created`, `document.status_changed`, `document.edited`, `payment.settled`, `payment.failed`, `finance.offer.received`, `finance.agreement.accepted`, `finance.agreement.funded`, `finance.agreement.repaid`, `finance.agreement.closed`, … 12 values[] |  | `audit.event` is the organization's security audit stream (one delivery per recorded event — sign-ins, key and member changes, sensitive reads) for a SIEM. It is OPT-IN ONLY: an empty `events` list subscribes to every document and payment event but never to `audit.event`; name it explicitly, and narrow it with `audit_categories`. The `data.event` object is the same line the audit export produces, including `seq`, `prevHash` and `hash`. |
| `body.audit_categories` | enum: `auth`, `session`, `admin`, `security_config`, `data_access`, `data_change`, `api_key`, `sso`, `scim`, `key_mgmt`, … 13 values[] |  |  |
| `body.description` | string \| null |  | ≤ 200 chars |

### getWebhookEndpoint

**Get a webhook endpoint** · `GET /v1/webhooks/{webhookId}` · effect **read** · scope `keys:manage` · family `webhooks` (behind `API_WEBHOOKS_ENABLED`) · auth: key or admin session

One of your organization's webhook endpoints: its URL, subscribed
events, `status` and `disabled_reason`, and when it last succeeded and
failed. The signing secret is never returned; it is shown only at
creation and by `rotateWebhookSecret`. An endpoint that isn't yours
answers `not_found`.

| Input | Type | Required | Notes |
|---|---|---|---|
| `webhookId` | id (24 hex) | yes | A Rivet resource id. |

### updateWebhookEndpoint

**Update a webhook endpoint** · `PATCH /v1/webhooks/{webhookId}` · effect **admin** · scope `keys:manage` · family `webhooks` (behind `API_WEBHOOKS_ENABLED`) · auth: key or admin session

Change the URL, description or event filter, disable it, or re-enable it (re-enabling resets the failure counter).

| Input | Type | Required | Notes |
|---|---|---|---|
| `webhookId` | id (24 hex) | yes | A Rivet resource id. |
| `body.url` | string (uri) |  | ≤ 2000 chars |
| `body.events` | enum: `document.created`, `document.status_changed`, `document.edited`, `payment.settled`, `payment.failed`, `finance.offer.received`, `finance.agreement.accepted`, `finance.agreement.funded`, `finance.agreement.repaid`, `finance.agreement.closed`, … 12 values[] |  | `audit.event` is the organization's security audit stream (one delivery per recorded event — sign-ins, key and member changes, sensitive reads) for a SIEM. It is OPT-IN ONLY: an empty `events` list subscribes to every document and payment event but never to `audit.event`; name it explicitly, and narrow it with `audit_categories`. The `data.event` object is the same line the audit export produces, including `seq`, `prevHash` and `hash`. |
| `body.audit_categories` | enum: `auth`, `session`, `admin`, `security_config`, `data_access`, `data_change`, `api_key`, `sso`, `scim`, `key_mgmt`, … 13 values[] |  |  |
| `body.description` | string \| null |  | ≤ 200 chars |
| `body.status` | enum: `ACTIVE`, `DISABLED` |  |  |

### deleteWebhookEndpoint

**Delete a webhook endpoint** · `DELETE /v1/webhooks/{webhookId}` · effect **admin** · scope `keys:manage` · family `webhooks` (behind `API_WEBHOOKS_ENABLED`) · auth: key or admin session

Deletes the endpoint and its stored deliveries. Irreversible.

| Input | Type | Required | Notes |
|---|---|---|---|
| `webhookId` | id (24 hex) | yes | A Rivet resource id. |

### rotateWebhookSecret

**Rotate the signing secret** · `POST /v1/webhooks/{webhookId}/rotate_secret` · effect **admin** · scope `keys:manage` · family `webhooks` (behind `API_WEBHOOKS_ENABLED`) · auth: key or admin session

Mints a new secret (returned once). The previous secret keeps signing for 24 hours (`previous_secret_valid_until`): inside that window every delivery carries TWO `v1=` entries in `Rivet-Webhook-Signature` — the new secret first, the previous one second — so a receiver that verifies against any entry can switch secrets without a single failed verification. After the window only the new secret signs.

| Input | Type | Required | Notes |
|---|---|---|---|
| `webhookId` | id (24 hex) | yes | A Rivet resource id. |

### pingWebhookEndpoint

**Send a test event** · `POST /v1/webhooks/{webhookId}/ping` · effect **admin** · scope `keys:manage` · family `webhooks` (behind `API_WEBHOOKS_ENABLED`) · auth: key or admin session

Delivers a `ping` event to the endpoint right now, signed like every delivery, and reports what happened — the way to verify your signature code before real traffic.

| Input | Type | Required | Notes |
|---|---|---|---|
| `webhookId` | id (24 hex) | yes | A Rivet resource id. |

### listWebhookDeliveries

**Recent deliveries** · `GET /v1/webhooks/{webhookId}/deliveries` · effect **read** · scope `keys:manage` · family `webhooks` (behind `API_WEBHOOKS_ENABLED`) · auth: key or admin session

The endpoint's recent deliveries (newest first, up to 50) — status, attempts, what the receiver answered. Deliveries are kept for 30 days.

| Input | Type | Required | Notes |
|---|---|---|---|
| `webhookId` | id (24 hex) | yes | A Rivet resource id. |
| `limit` | integer |  | Page size (1–100, default 20). · 1–100 · default 20 |

### redeliverWebhook

**Redeliver** · `POST /v1/webhooks/{webhookId}/deliveries/{deliveryId}/redeliver` · effect **admin** · scope `keys:manage` · family `webhooks` (behind `API_WEBHOOKS_ENABLED`) · auth: key or admin session

Sends a stored delivery's exact payload again as a fresh delivery with a fresh signature — the replay path for anything missed while an endpoint was down or its secret was wrong. The new delivery has a new `Rivet-Webhook-Id`; the event's `id` in the body is unchanged, so a receiver that dedupes on it processes the event once.

| Input | Type | Required | Notes |
|---|---|---|---|
| `webhookId` | id (24 hex) | yes | A Rivet resource id. |
| `deliveryId` | id (24 hex) | yes | A Rivet resource id. |

## Provenance

A document's verified history and lineage, and the exportable proof
bundle. Every read is scoped to your side exactly like the document read:
a document on a connection your organization is not a party to answers
`not_found`, and a lineage step on the counterparty's own side appears
only as a SEALED entry — a hash and nothing else. Existence is proven;
content is withheld. The export bundle is designed to be verified
OFFLINE, without trusting Rivet — see the docs' verify script.

### getDocumentHistory

**A document's verified history** · `GET /v1/documents/{documentId}/history` · effect **read** · scope `provenance:read` · family `provenance` (behind `API_PROVENANCE_ENABLED`) · auth: key

Every state the document has been through, in order — who acted (from
your side's point of view), when, and whether the underlying hash chain
verifies. `verified` is the graph's own integrity check, recomputed on
every read. A document adopted mid-life carries a `baseline` marker:
history before adoption was not witnessed and is named as such, never
backfilled. A document your organization is not a party to answers
`not_found`.

| Input | Type | Required | Notes |
|---|---|---|---|
| `documentId` | id (24 hex) | yes | A Rivet resource id. |

### getDocumentLineage

**A document's lineage** · `GET /v1/documents/{documentId}/lineage` · effect **read** · scope `provenance:read` · family `provenance` (behind `API_PROVENANCE_ENABLED`) · auth: key

The documents this one came from (`direction=up`, the default), the
documents that came from it (`down`), or both — as nodes and edges,
bounded by the server's depth/breadth caps. A step on the counterparty's
own side is a SEALED node: `{sealed, transition_hash, occurred_at}` —
existence and integrity, no content, no onward expansion. Downstream
lineage answers `not_enabled` until the reverse index is deployed here.

| Input | Type | Required | Notes |
|---|---|---|---|
| `documentId` | id (24 hex) | yes | A Rivet resource id. |
| `direction` | enum: `up`, `down`, `both` |  | default "up" |
| `depth` | integer |  | Hops to expand. Clamped to the server's cap; oversized results shrink depth before they truncate. · 1–4 · default 1 |

### exportDocumentProvenance

**Export the verifiable proof bundle** · `GET /v1/documents/{documentId}/export` · effect **read** · scope `provenance:read` · family `provenance` (behind `API_PROVENANCE_ENABLED`) · auth: key

The document's ancestry as a self-describing, offline-verifiable
artifact — the mechanism for sharing verified history with a lender or
auditor WITHOUT giving them API access: hand them the file; they verify
it with the published script (or any SHA-256 implementation and the
bundle's own `verification` block). The body is canonical JSON —
recursively key-sorted, no volatile fields — so the same document
exports byte-identically until its own ancestry changes;
`Rivet-Bundle-Digest` is the sha256 of the exact bytes. Ancestry only,
by design: a bundle proves where a document came from as of issuance;
what later came OF it is a live query, never proof material. Steps on a
counterparty's own side appear only in `sealed` — hashes without
content. Exports have their own small per-key daily budget
(`rate_limited` when spent); an ancestry larger than the server's
export limit answers `conflict` — a bundle is complete or absent,
never truncated.

| Input | Type | Required | Notes |
|---|---|---|---|
| `documentId` | id (24 hex) | yes | A Rivet resource id. |

## Finance

Rivet Finance: the Verification Grade — a deterministic, fully-explained
reading of the bilateral record — the signed, identity-free attestation a
funder can verify without membership, and **direct financing between
verified members** with settlement-linked repayment. A borrower lists a
receivable; a lender offers at the ask or counters; acceptance creates a
bilateral document set (Agreement · Disbursement · Repayment) on the
parties' own connection. Every leg is one organization's account to the
other's on the same rails — Rivet holds no funds, nets nothing, routes
nothing through itself, and initiates exactly one thing: when the pledged
invoice settles through Rivet, a payment on the Repayment for the lesser
of the proceeds and the amount owed, under the borrower's standing
authorization. Rivet is the interface and attestor; the terms are the
parties'; Rivet does arithmetic only.

### getDocumentAttestation

**The signed attestation (Rivet Finance)** · `GET /v1/documents/{documentId}/attestation` · effect **read** · scope `provenance:read` · family `finance` (behind `API_FINANCE_ENABLED`) · auth: key

The identity-free profile of the export bundle, signed by Rivet
(the trusted attestor — ruling F1): states, timestamps, hash anchors and
the Verification Grade, with every identity field redacted — actor ids
become issuer/receiver roles, free-text and metadata are dropped,
document ids become positional refs. Identity fields sit inside the
transition-hash preimage, so this profile deliberately does NOT support
per-transition hash recomputation; its proof is the signature over the
canonical body (the `verification` block inside says exactly this).
`?amounts=bucketed` coarsens the subject amount to a bucket (F4).
The body's canonical bytes are content-addressed: `Rivet-Bundle-Digest`
is their sha256, and the digest can be verified WITHOUT membership at
`GET /public/verify/{digest}` (keyless, rate-limited, non-enumerating).
Attestations are signed with ES256 from AWS KMS; those issued before
the key-custody cutover are EdDSA (Ed25519) under the retired kid
`att-hkdf-v1`. `signature.alg` says which, and `verify-attestation.mjs`
verifies both.

| Input | Type | Required | Notes |
|---|---|---|---|
| `documentId` | id (24 hex) | yes | A Rivet resource id. |
| `amounts` | enum: `exact`, `bucketed` |  | default "exact" |

### financeOptIn

**Opt the organization in or out of the marketplace** · `POST /v1/finance/opt-in` · effect **write** · scope `finance:write` · family `marketplace_writes` (behind `FINANCE_MARKETPLACE_ENABLED`) · auth: key

Opting in mints the organization's stable funding-profile pseudonym (kept on later re-opt-ins). Sandbox organizations always; live organizations where `marketplace_live` is available (see GET /v1/enablement).

| Input | Type | Required | Notes |
|---|---|---|---|
| `body.opt_in` | boolean | yes |  |

### listFinanceEligible

**Your invoices, with their listing eligibility** · `GET /v1/finance/eligible` · effect **read** · scope `finance:read` · family `marketplace` (behind `FINANCE_MARKETPLACE_ENABLED`) · auth: key

Every fundable-status invoice you issued, each with its F8 evaluation — eligible or not, the grade, and every check named.

_No inputs._

### browseFinanceListings

**Browse open listings (pseudonymous)** · `GET /v1/finance/listings` · effect **read** · scope `finance:read` · family `marketplace` (behind `FINANCE_MARKETPLACE_ENABLED`) · auth: key

The authenticated, PSEUDONYMOUS browse — grades, facts and stated asks; never identities. Identity reaches one funder only through the lister's explicit consent.

_No inputs._

### createFinanceListing

**List an eligible invoice** · `POST /v1/finance/listings` · effect **write** · scope `finance:write` · family `marketplace_writes` (behind `FINANCE_MARKETPLACE_ENABLED`) · auth: key

The F8 floor is enforced server-side (a refusal names every failed
check) and the identity-free attestation is minted at list time. The
ask terms are numbers YOU state — Rivet computes and recommends
nothing.

| Input | Type | Required | Notes |
|---|---|---|---|
| `body.document_id` | id (24 hex) | yes | A Rivet resource id. |
| `body.ask_terms` | object |  | Numbers YOU state. The loan-terms half (principal · fee · grace_days) is what a lender offers at or counters on; Rivet computes only fee amount and owed = principal + fee. |
| `body.ask_terms.advance_bps` | number \| null |  |  |
| `body.ask_terms.tenor_days` | number \| null |  |  |
| `body.ask_terms.principal` | number \| null |  | The advance asked, at most the invoice's amount. |
| `body.ask_terms.fee_mode` | enum: `flat`, `bps` · or null |  | A fixed fee: a flat amount, or basis points of principal. |
| `body.ask_terms.fee_value` | number \| null |  |  |
| `body.ask_terms.grace_days` | integer \| null |  | Days after the pledged invoice's due date before the repayment is past due (informational — Rivet collects nothing). |
| `body.amounts` | enum: `exact`, `bucketed` |  | default "exact" |
| `body.use_profile_pseudonym` | boolean |  | default false |

### withdrawFinanceListing

**Withdraw a listing** · `DELETE /v1/finance/listings/{listingId}` · effect **write** · scope `finance:write` · family `marketplace_writes` (behind `FINANCE_MARKETPLACE_ENABLED`) · auth: key

Takes your own open listing off the marketplace: its status becomes
`withdrawn`, and funders no longer see it. Only an open listing your
organization created can be withdrawn; any other id answers
`not_found`.

| Input | Type | Required | Notes |
|---|---|---|---|
| `listingId` | id (24 hex) | yes | A Rivet resource id. |

### listFinanceListingInterests

**The interests on your listing (funders pseudonymous)** · `GET /v1/finance/listings/{listingId}/interests` · effect **read** · scope `finance:read` · family `marketplace` (behind `FINANCE_MARKETPLACE_ENABLED`) · auth: key

The interests funders have expressed on your listing, oldest first.
Each funder is labeled by position (`funder_1`, `funder_2`, …) and stays
pseudonymous until you consent to it with `consentFinanceInterest`. The
labels are positions in this list, not stable names, so refer to an
interest by its `id`. Withdrawn interests aren't listed. A listing your
organization didn't create answers `not_found`.

| Input | Type | Required | Notes |
|---|---|---|---|
| `listingId` | id (24 hex) | yes | A Rivet resource id. |

### expressFinanceInterest

**Express interest in a listing** · `POST /v1/finance/listings/{listingId}/interest` · effect **write** · scope `finance:write` · family `marketplace_writes` (behind `FINANCE_MARKETPLACE_ENABLED`) · auth: key

The lister learns interest exists — pseudonymously. Your identity reaches them only if THEY consent to disclosure (which discloses the LISTER to you; F6).

| Input | Type | Required | Notes |
|---|---|---|---|
| `listingId` | id (24 hex) | yes | A Rivet resource id. |

### withdrawFinanceInterest

**Withdraw your interest** · `DELETE /v1/finance/interests/{interestId}` · effect **write** · scope `finance:write` · family `marketplace_writes` (behind `FINANCE_MARKETPLACE_ENABLED`) · auth: key

Withdraws an interest your organization expressed, while it is still
`expressed`. Once the lister has consented and your identity is
disclosed, it can no longer be withdrawn: that, like an id that isn't
yours, answers `not_found`.

| Input | Type | Required | Notes |
|---|---|---|---|
| `interestId` | id (24 hex) | yes | A Rivet resource id. |

### consentFinanceInterest

**Consent to disclosure (the lister's act)** · `POST /v1/finance/interests/{interestId}/consent` · effect **write** · scope `finance:write` · family `marketplace_writes` (behind `FINANCE_MARKETPLACE_ENABLED`) · auth: key

expressed → disclosed, once. Both sides are audited; the funder may then read the disclosure. This is the END of the platform's flow (F6).

| Input | Type | Required | Notes |
|---|---|---|---|
| `interestId` | id (24 hex) | yes | A Rivet resource id. |

### getFinanceDisclosure

**The disclosed introduction (the funder's read)** · `GET /v1/finance/interests/{interestId}/disclosure` · effect **read** · scope `finance:read` · family `marketplace` (behind `FINANCE_MARKETPLACE_ENABLED`) · auth: key

Available only after the lister consented. Identity plus the attestation reference — and Rivet's role ends here (F6); any transaction proceeds off-platform.

| Input | Type | Required | Notes |
|---|---|---|---|
| `interestId` | id (24 hex) | yes | A Rivet resource id. |

### getFinanceSetup

**Your finance setup** · `GET /v1/finance/setup` · effect **read** · scope `finance:read` · family `marketplace` (behind `FINANCE_MARKETPLACE_ENABLED`) · auth: key

Verification, the accounts you named on each rail, the accounts money
knows for your organization, your readiness as a borrower and as a
lender (every failed check named), the standing-authorization template
a borrower accepts, and Rivet's role in one line.

_No inputs._

### setFinanceRails

**Name your accounts on a rail** · `PUT /v1/finance/setup` · effect **write** · scope `finance:write` · family `marketplace_writes` (behind `FINANCE_MARKETPLACE_ENABLED`) · auth: key

Your OWN accounts (F10): a settlement account (where your pledged
invoices land — the repayment sweep draws from it) and/or a funding
account (what you pay principal from — the sweep lands in it). Live
fiat ids must be among the accounts money reports for you; sandbox
organizations carry simulated ids. Rivet holds nothing and routes
through nothing of its own.

| Input | Type | Required | Notes |
|---|---|---|---|
| `body.rail` | enum: `fiat`, `usdc` | yes |  |
| `body.settlement_account_id` | string \| null |  |  |
| `body.funding_account_id` | string \| null |  |  |

### listFinanceOffers

**Offers, both directions** · `GET /v1/finance/offers` · effect **read** · scope `finance:read` · family `marketplace` (behind `FINANCE_MARKETPLACE_ENABLED`) · auth: key

The offers you RECEIVED on your listings (lenders pseudonymous until you accept one) and the offers you SENT.

| Input | Type | Required | Notes |
|---|---|---|---|
| `listing_id` | id (24 hex) |  | A Rivet resource id. |

### createFinanceOffer

**Offer on a listing** · `POST /v1/finance/listings/{listingId}/offers` · effect **write** · scope `finance:write` · family `marketplace_writes` (behind `FINANCE_MARKETPLACE_ENABLED`) · auth: key

At the listing's ask (`at_ask: true`), or a counter on `principal`
and/or `fee` (F13 — Rivet computes only the fee amount and owed =
principal + fee; grace days are the borrower's). Gated (F15): you are
verified, on Rivet rails, with a funding account named on the listing's
rail — a refusal names every failed check. One pending offer per lender
per listing; the borrower accepts one (F12).

| Input | Type | Required | Notes |
|---|---|---|---|
| `listingId` | id (24 hex) | yes | A Rivet resource id. |
| `body.at_ask` | boolean |  | default false |
| `body.principal` | number \| null |  |  |
| `body.fee` | object \| null |  |  |
| `body.fee.mode` | enum: `flat`, `bps` |  |  |
| `body.fee.value` | number |  |  |

### getFinanceOffer

**One offer** · `GET /v1/finance/offers/{offerId}` · effect **read** · scope `finance:read` · family `marketplace` (behind `FINANCE_MARKETPLACE_ENABLED`) · auth: key

The borrower's read includes the standing authorization (text + digest) accepting the offer requires; the lender's read is its own offer.

| Input | Type | Required | Notes |
|---|---|---|---|
| `offerId` | id (24 hex) | yes | A Rivet resource id. |

### withdrawFinanceOffer

**Withdraw your pending offer** · `DELETE /v1/finance/offers/{offerId}` · effect **write** · scope `finance:write` · family `marketplace_writes` (behind `FINANCE_MARKETPLACE_ENABLED`) · auth: key

Withdraws an offer your organization made, while it is still
`pending`. An offer that is no longer pending, or one that isn't yours,
answers `not_found`.

| Input | Type | Required | Notes |
|---|---|---|---|
| `offerId` | id (24 hex) | yes | A Rivet resource id. |

### acceptFinanceOffer

**Accept an offer (creates the agreement)** · `POST /v1/finance/offers/{offerId}/accept` · effect **write** · scope `finance:write` · family `marketplace_writes` (behind `FINANCE_MARKETPLACE_ENABLED`) · auth: key

The borrower accepts ONE offer. Both sides are re-checked at the gate
(F15), the rail must still match the pledged invoice's (F14), the
invoice must be fundable and unfinanced (F12), and the standing
authorization must be echoed by its digest (read it on the offer).
Acceptance creates, all-or-nothing: the lender↔borrower connection if
absent, the Agreement (FIN-), the Disbursement (DSB-, the lender's
payable — its settlement is principal delivered) and the Repayment
(RPY-, the borrower's payable; active once funded). The listing closes;
every other pending offer closes. A missing digest answers `conflict`
with `details.authorization` carrying the text and digest.

| Input | Type | Required | Notes |
|---|---|---|---|
| `offerId` | id (24 hex) | yes | A Rivet resource id. |
| `body.authorization_digest` | string | yes | sha256 of the authorization text exactly as GET /v1/finance/offers/{offerId} returned it. |

### listFinanceAgreements

**Your agreements, both sides** · `GET /v1/finance/agreements` · effect **read** · scope `finance:read` · family `marketplace` (behind `FINANCE_MARKETPLACE_ENABLED`) · auth: key

Direction-aware — "Financing provided" where you are the lender, "Financing received" where you are the borrower.

| Input | Type | Required | Notes |
|---|---|---|---|
| `status` | enum: `accepted`, `funded`, `repaying`, `delinquent`, `flagged`, `closed`, `cancelled` |  |  |

### getFinanceAgreement

**One agreement** · `GET /v1/finance/agreements/{agreementId}` · effect **read** · scope `finance:read` · family `marketplace` (behind `FINANCE_MARKETPLACE_ENABLED`) · auth: key

One financing agreement, as your side sees it. Only its two parties can
read it, and `direction` says which one you are (`lender` or
`borrower`); any other organization gets `not_found`.

| Input | Type | Required | Notes |
|---|---|---|---|
| `agreementId` | id (24 hex) | yes | A Rivet resource id. |

### withdrawFinanceAgreement

**Withdraw before funding (either party)** · `POST /v1/finance/agreements/{agreementId}/withdraw` · effect **write** · scope `finance:write` · family `marketplace_writes` (behind `FINANCE_MARKETPLACE_ENABLED`) · auth: key

Until the lender pays the Disbursement, either party may withdraw — the agreement is cancelled and both payables are voided. After funding there is no withdrawal; the obligation is repaid.

| Input | Type | Required | Notes |
|---|---|---|---|
| `agreementId` | id (24 hex) | yes | A Rivet resource id. |
| `body.reason` | string \| null |  | ≤ 500 chars |

### getFinanceRepayment

**Repayment status** · `GET /v1/finance/agreements/{agreementId}/repayment` · effect **read** · scope `finance:read` · family `marketplace` (behind `FINANCE_MARKETPLACE_ENABLED`) · auth: key

Owed, paid, remaining, the due date, and every sweep — what settled, what was swept, and why a settlement was not.

| Input | Type | Required | Notes |
|---|---|---|---|
| `agreementId` | id (24 hex) | yes | A Rivet resource id. |

## Sandbox

A safe, isolated pair of organizations to practice against: "{Your org}
Sandbox" connected to "Riveted Demo Co", seeded with a catalog and
documents. Your sandbox comes with a counterparty — the advance endpoint
performs its next legal action so you can drive both sides of a document's
life. Sandbox keys are `rk_test_…`; they never touch live data, and live
keys never touch the sandbox.

### getSandbox

**Your sandbox's state** · `GET /v1/sandbox` · effect **read** · scope `keys:manage` · family `sandbox` (behind `API_SANDBOX_ENABLED`) · auth: key or admin session

Your sandbox's `status`, the sandbox organization your `rk_test_` keys
act as, its demo counterparties by role (`supplier`, `buyer`) with the
`connection_id` to create documents on, document counts and quota.
Answers `not_found` until a sandbox is provisioned with
`provisionSandbox`.

_No inputs._

### provisionSandbox

**Create your sandbox** · `POST /v1/sandbox` · effect **admin** · scope `keys:manage` · family `sandbox` (behind `API_SANDBOX_ENABLED`) · auth: key or admin session

Provisions the pair — "{Your org} Sandbox" connected to "Riveted Demo
Co" — seeded with a small catalog, one incoming invoice (your first
read is a Bill) and one draft. One sandbox per organization: calling
again returns the existing one (200). Sandbox keys are minted on the
sandbox organization afterwards via `POST /v1/keys` with its admin
session — or simply use this key's org-scoped access via the returned
ids.

| Input | Type | Required | Notes |
|---|---|---|---|
| `body.name` | string |  | What to call the sandbox organization. Defaults to "{Your org} Sandbox". The counterparty is always "Riveted Demo Co". · 1–60 chars |

### deleteSandbox

**Tear your sandbox down** · `DELETE /v1/sandbox` · effect **admin** · scope `keys:manage` · family `sandbox` (behind `API_SANDBOX_ENABLED`) · auth: key or admin session

Deletes the pair and everything in it — organizations, connection, documents, items, and the sandbox's keys. Irreversible; a new sandbox can be provisioned afterwards.

_No inputs._

### resetSandbox

**Reset your sandbox** · `POST /v1/sandbox/reset` · effect **admin** · scope `keys:manage` · family `sandbox` (behind `API_SANDBOX_ENABLED`) · auth: key or admin session

Wipes the sandbox's documents and catalog and reseeds the originals. The organizations, connection and keys survive. Safe to repeat.

_No inputs._

### setSandboxMoney

**Turn simulated Rivet Money on or off in your sandbox** · `PUT /v1/sandbox/money` · effect **admin** · scope `keys:manage` · family `sandbox` (behind `API_SANDBOX_ENABLED`) · auth: key or admin session

Simulated Rivet Money gives your sandbox Treasury accounts with balances,
Wallet payment methods, Cards (issue, list, freeze) and payments through
Rivet between sandbox organizations, including a payment released by
hand in the app. Everything stays in the simulation: nothing reaches a
bank and no real money moves. It can be turned on only while your
organization is verified by Rivet — otherwise `owner_not_verified` — and
it reads off whenever your organization is not verified. Turning it off
is always allowed. The answer is your sandbox's state, with `money`.

| Input | Type | Required | Notes |
|---|---|---|---|
| `body.enabled` | boolean | yes | `true` turns simulated Rivet Money on; `false` turns it off. |

### advanceSandboxCounterparty

**The counterparty acts** · `POST /v1/sandbox/counterparty/advance` · effect **admin** · scope `keys:manage` · family `sandbox` (behind `API_SANDBOX_ENABLED`) · auth: key or admin session

The document's own counterparty performs its next legal action on it —
acknowledge, approve, whatever the document's own state machine offers
that side right now — through the same service layer the app uses.
Riveted Demo Buyers acts on the invoices you issued it (it acknowledges,
approves, then pays — a simulated settlement, and `payment.settled`
fires); Riveted Demo Co acts on the bills it sent you only once you have
paid one: on a `payment_scheduled` bill it confirms the settlement and
the bill reads `paid`. It never takes a system-only edge, so a bill you
have not approved is never marked paid behind you. No timers, no
automation: it acts only when you call this. The response names the
action taken; when the counterparty has no legal action, `conflict` with
`details.allowed` (which may be empty) — and, on a bill that is your move,
a message that says so.

| Input | Type | Required | Notes |
|---|---|---|---|
| `body.document_id` | id (24 hex) |  | A Rivet resource id. |
| `body.listing_id` | id (24 hex) |  | WS-FINANCE F2 — instead of a document: Riveted Demo Co (as lender) offers on your sandbox listing at its ask. |

## Share

Add to a person's share drive: an HTML page (a page Claude or another
agent made: an artifact, a report, a mockup) or a link. Every Share
operation acts as the **person who created the key**, never the
organization, because a share item is a person's: they must still be able
to share for the organization, and the item lands in their **My drive**,
restricted, for them to share with the people they choose. A page is
checked and scanned like any upload, and Rivet shows it in its own viewer,
in a sandbox. Answers never carry an item's address: it holds the item's
share token, and an agent's transcript is no place for one.

### createShareItem

**Add an HTML page or a link to the share drive** · `POST /v1/share/items` · effect **write** · scope `share:write` · family `share` (behind `API_SHARE_ENABLED`) · auth: key

Adds an item to the share drive of the person who created this key, in
the key's organization: restricted (only the people they invite can
open it), in their **My drive** at `drive_url`, for them to share.

**An HTML page** (`kind: html`) is the whole document in `html`, UTF-8,
up to 4 MB: a page an agent made, such as a Claude artifact, a report or
a mockup. Rivet checks that it reads as HTML and scans it for malware
like any upload: `status` is `scanning` until the scan is clean, then
`ready`. Rivet shows it in its viewer, in a sandbox: its scripts run,
and nothing they run can reach Rivet.

**A link** (`kind: link`) is an `https` address. Rivet's page for it
shows where it goes before a reader leaves.

The answer never carries the item's address, which holds its share
token: the person shares it from the drive. The person must still be
able to share for the organization (`forbidden` if not); a sandbox
organization has no drive (`capability_unavailable`).

| Input | Type | Required | Notes |
|---|---|---|---|
| `body.kind` | enum: `html`, `link` | yes | `html`: a page, in `html`. `link`: an address, in `url`. |
| `body.title` | string | yes | What the drive and its readers call it. · 1–120 chars |
| `body.html` | string |  | `html` only: the whole page, UTF-8, up to 4 MB. · 1–4194304 chars |
| `body.url` | string (uri) |  | `link` only: an https address. · ≤ 2048 chars |
| `idempotency_key` | string |  | A stable key for this logical action (8–128 chars), e.g. invoice-ACME-1042. Send the SAME key when you retry after a timeout — a new key is a new write. Generated per call when omitted. |

### getShareItem

**Get a share item** · `GET /v1/share/items/{itemId}` · effect **read** · scope `share:write` · family `share` (behind `API_SHARE_ENABLED`) · auth: key

A page or a link in the drive of the person who created this key: what
it is, and whether a page has passed its malware scan (`status`). An
item in another organization, one the person may not manage, or
anything this family doesn't add (a deck, a site, another kind of file)
answers `not_found`.

| Input | Type | Required | Notes |
|---|---|---|---|
| `itemId` | string | yes | The item's id, as `createShareItem` answered it. |

## Keys

Manage the organization's API keys. Admin-only: either a member session
(an org admin's bearer token) or a key that carries the `keys:manage` scope.

### listApiKeys

**List the organization's API keys** · `GET /v1/keys` · effect **read** · scope `keys:manage` · family `keys` · auth: key or admin session

Every key ever minted for the organization, revoked ones included (with `revoked_at` set). Secrets are never returned.

_No inputs._

### createApiKey

**Create an API key** · `POST /v1/keys` · effect **admin** · scope `keys:manage` · family `keys` · auth: key or admin session

Mints a new key for the caller's organization. **The full key is
returned exactly once**, in this response — store it securely. Only
its SHA-256 hash and a displayable prefix are kept.

To replace a key, rotate it (`POST /v1/keys/{keyId}/rotate`): the
replacement carries the old key's name, scopes, payment limits and
allowed ranges, and the old key keeps working for up to 24 hours while
your integration moves over.

| Input | Type | Required | Notes |
|---|---|---|---|
| `body.name` | string | yes | A label for the key (e.g. the system that will use it). · 1–80 chars |
| `body.scopes` | enum: `documents:read`, `documents:write`, `payments:read`, `payments:write`, `treasury:read`, `provenance:read`, `finance:read`, `finance:write`, `keys:manage`, `share:write`[] | yes | - `documents:read` — Documents family reads - `documents:write` — create, edit (drafts), issue and transition documents - `payments:read` — Payments family - `payments:write` — execute document payments: the fiat rail (schedule/mark paid; live organizations only once fiat payments are enabled, always available in sandbox) and the USDC road of `payDocument` (`payment_method: usdc`; wherever the `pay` family is on, the fiat switch aside) - `treasury:read` — Treasury family reads (verified organizations only; never in sandbox) - `provenance:read` — verified history, lineage and the export bundle (your own documents; sandbox included) - `finance:read` / `finance:write` — the Rivet Finance marketplace (sandbox organizations always; live organizations where `marketplace_live` is available — otherwise a live organization answers `capability_unavailable`) - `keys:manage` — key and sandbox management (admin scope; grantable only from an admin session) - `share:write` — add an HTML page or a link to the share drive of the person who creates the key, and read those items back (live organizations; a sandbox has no drive) · ≥ 1 items |
| `body.payment_limits` | object \| null |  | Only accepted on a key granted payments:write. |
| `body.payment_limits.max_per_payment` | number \| null |  | The largest single payment this key may schedule. · > 0 |
| `body.payment_limits.daily_volume` | number \| null |  | The most this key may schedule per UTC day · > 0 |
| `body.environment` | enum: `live`, `sandbox` |  | Which organization the key is minted under. `sandbox` mints it under THIS organization's sandbox — the sandbox is owned by this organization, so the same `keys:manage` authority covers it, and you do not have to switch workspace first. Answers `conflict` when no sandbox has been provisioned yet. A sandbox organization cannot mint a `live` key. · default "live" |
| `body.expires_at` | string (date-time) |  | Optional. The key is refused with `unauthorized` once this instant has passed. Must be in the future. |
| `body.allowed_cidrs` | string[] |  | Optional. When present, requests with this key are accepted only from a client address inside one of these ranges (`forbidden` otherwise). The address is the one the platform resolves through its trusted proxy hops. · ≤ 20 items |

### revokeApiKey

**Revoke an API key** · `DELETE /v1/keys/{keyId}` · effect **admin** · scope `keys:manage` · family `keys` · auth: key or admin session

Revocation is immediate and permanent; the key's next request is refused with `unauthorized`. Revoking an already-revoked key is a no-op that returns the key.

| Input | Type | Required | Notes |
|---|---|---|---|
| `keyId` | id (24 hex) | yes | A Rivet resource id. |

### rotateApiKey

**Rotate an API key with a 24-hour grace** · `POST /v1/keys/{keyId}/rotate` · effect **admin** · scope `keys:manage` · family `keys` · auth: key or admin session

Mints a replacement with the same name, scopes, payment limits and
allowed ranges, and returns it **with its secret exactly once**. The
key being rotated keeps working for **24 hours** (its `expires_at` is
set to that instant, unless it was already earlier) so an integration
can move over without downtime, then is refused with `unauthorized`.
Both keys record the lineage (`rotated_from_id` / `rotated_to_id`)
and both sides are audited. A revoked or already-expired key cannot be
rotated — create a new one.

| Input | Type | Required | Notes |
|---|---|---|---|
| `keyId` | id (24 hex) | yes | A Rivet resource id. |
