# Rivet for agents

> For an AI agent that will read or act on a company's Rivet record, and for the developer wiring one up. Read this page first. It covers the model in five facts, how to connect, the first calls, a recipe for each common task, and what to do with every error. Everything it names is served by this API unless it is marked **Coming**. To see which parts this deployment has switched on, check `https://api.rivet.network/v1/enablement`.

## What is live, and what isn't

| Part | Status | How to check at runtime |
|---|---|---|
| The v1 API: documents, payments, treasury, document writes, webhooks, sandbox, provenance, finance | Live where this deployment serves it | `GET /v1/enablement`. Each family reports `available`. On a live organization, writes, payments and Finance each have their own switch as well, and a refusal says which: `not_enabled` or `capability_unavailable`. |
| Pay by agent: paying a Rivet invoice in USDC with x402 | Live where `families.pay.available` is true | `GET /v1/enablement` |
| The Rivet MCP server (`@rivet-network/mcp`): the API as tools for your agent | Live | `npx -y @rivet-network/mcp` |
| Connect: providers charging agents per request (x402 resources) | **Coming** | No endpoint exists |
| Connect: agent identity on calls to providers | **Coming** | No endpoint exists |
| Connect: providers' own tools, discoverable by Gio and other agents | **Coming** | No endpoint exists |
| Gio paying providers on a business's behalf | **Coming** | No endpoint exists |

Anything marked **Coming** has no endpoint, header or tool behind it, so don't construct a request for it. The plan is on the Connect page of the Rivet site: `https://rivet.network/developers/connect`.

## 1. The model in five facts

1. **A connection is one relationship.** It joins your organization and one counterparty. Every document lives on exactly one connection: invoices, purchase orders, quotes, delivery confirmations, shipment notices, bills of sale, settlement statements and the rest.
2. **One record, two sides.** The same invoice is an `Invoice` numbered `INV-0042` to the company that issued it, and a `Bill` numbered `BILL-0042` to the company that received it.
   - These fields depend on who is reading: `direction`, `label`, `number`, `display_status`, `linked_system_number`, the labels and numbers inside `parent_documents`, and `pay_url`, which appears only on the payer's view.
   - Everything else is identical on both sides: id, status, lines, totals and dates.
3. **Your key is your organization.** There is no organization id to pass, and every answer is projected for your side. The one exception is the share drive: `createShareItem` and `getShareItem` act as the person who made the key, because a share item is a person's. A document on a connection you are not a party to answers `not_found`; its existence is never acknowledged. The counterparty's draft answers the same `not_found`: a draft belongs to its issuer alone until it is issued.
4. **`status` and `phase` answer different questions.**
   - `status` is the document's own state machine, in lowercase, and differs by type. An invoice runs `draft` → `submitted` → `acknowledged` → `approved` → `payment_scheduled` → `paid`.
   - `phase` is one vocabulary across every type: `draft`, `awaiting_counterparty`, `in_review`, `approved`, `scheduled`, `overdue`, `exception`, `settled`, `closed`.
   - Use `phase` to ask "what is overdue?" across all types at once.
5. **There are three kinds of number.**
   - Document amounts are JSON numbers in the document's `currency`.
   - Treasury balances are exact decimal strings, never floats.
   - x402 amounts are atomic USDC strings with six decimals: `1250000000` is $1,250.00.

## 2. Connect your agent

### Over MCP

The fastest route for an agent. Add the server to any MCP client (Claude Code, Claude Desktop, Cursor):

```json
{
  "mcpServers": {
    "rivet": {
      "command": "npx",
      "args": ["-y", "@rivet-network/mcp"],
      "env": {
        "RIVET_API_KEY": "rk_live_…",
        "RIVET_API_BASE": "https://api.rivet.network"
      }
    }
  }
}
```

- **One tool per operation.** At startup the server reads this deployment's contract and serves one tool per operation, named by its `operationId`: `listDocuments`, `createDocument`, `payDocument` and so on. The tool list always matches what the environment serves.
- **Instructions on connect.** When your client connects, the server sends instructions: the model, the first call, and the rules below in short form.
- **Hints on every tool.** Each tool carries a title, its full description, and hints about what it does. `readOnlyHint` marks every read. `destructiveHint` marks anything that can move money, delete, revoke, or change a document's state.
- **Bodies only.** You see response bodies, not headers. `Retry-After`, `Idempotent-Replay`, `Rivet-Bundle-Digest` and `PAYMENT-RESPONSE` don't reach the agent; the recipes below say what to do instead.
- **The reference.** Every tool, with its scope and inputs: `https://api.rivet.network/v1/docs/mcp-tools.md`.
- **Where the key goes.** Put it only in the client's local configuration or environment. Never put it in a prompt, a repository, a log or a command line.

### Over HTTP

- Send `Authorization: Bearer <key>` on every call. Production is `https://api.rivet.network`; staging is `https://test.rivetx.app`.
- The contract is `GET /v1/openapi.json`. It needs no key, so an agent that reads OpenAPI can load it directly. The interactive reference is at `/v1/docs`.
- Every response carries `Rivet-Api-Version`. Authenticated responses also carry `X-RateLimit-Limit` and `X-RateLimit-Remaining`.
- Rate limits are per key:
  - 120 requests a minute and 10,000 a day; sandbox keys get 60 a minute.
  - Writes draw from their own bucket of 30 a minute.
  - Over a limit, you get `429` with `Retry-After`.

## 3. Give the agent the least it needs

A person mints the agent's key in **Settings → Developers** and picks its scopes. The key's prefix follows the organization, not the environment: a live organization's keys start `rk_live_…` and a sandbox organization's start `rk_test_…`. The two are hard-isolated, and a key used against the other kind of organization is refused with `sandbox_isolation`.

| The agent… | Scopes |
|---|---|
| answers questions about documents and money | `documents:read`, `payments:read`. Add `provenance:read` for verified history and `treasury:read` for balances (verified organizations only). |
| drafts, issues and moves documents | add `documents:write` |
| pays bills on the Rivet rail | `documents:write` and `payments:write`, with `payment_limits` (`max_per_payment`, `daily_volume`) and an `expires_at` set on the key |
| pays a bill in USDC (x402) | the same `documents:write` and `payments:write`, with `payment_limits`; the deployment's `pay` family must be on. The fiat payments switch doesn't apply |
| works with Rivet Finance listings and offers | `finance:read`; add `finance:write` to act |
| practices end to end in the sandbox | an `rk_test_` key with the scopes above plus `keys:manage`, which lets it drive the demo counterparties |
| puts a page it made, or a link, in a person's share drive | `share:write`, on a key that person mints (the items are theirs); live organizations only, as a sandbox has no drive |

- Don't give an agent `keys:manage` on a live organization; that scope mints keys.
- For an agent on a server with a fixed address, also set `allowed_cidrs`. A call from anywhere else is refused with `forbidden`, even with a valid key.
- A scope never exceeds what the organization has. A key with `treasury:read` in an organization that hasn't finished verification gets `capability_unavailable`, not an empty list.

## 4. The first calls

1. **`getEnablement`.** Which families are on in this deployment. A family that is off answers `not_enabled` on every call; say so, and don't retry.
2. **`listDocuments` with `limit` 5.** The shape. Note `direction` and `label` on each document.
3. **`getDocument`.** The detail: `line_items`, `parent_documents`, `verification_grade`, and `pay_url` on a received invoice whose payee accepts USDC from agents.
4. **`getPaymentsContext`.** What the organization owes (`payable`) and is owed (`receivable`), counted and totalled by status.

If you don't know whether the key is a sandbox (`rk_test_`) or a real business (`rk_live_`), ask the person before you write anything.

## 5. Recipes

Each recipe starts from something a person would say, then gives the calls in order and what to watch for.

### "What do we owe, and what's overdue?"

1. `getPaymentsContext`. `payable` is what you owe and `receivable` is what you are owed; `outstanding` sums every unpaid status.
2. `listDocuments` with `type=invoice` and `phase=overdue`. Keep the documents whose `direction` is `received`: those are your bills.
3. Page with `next_cursor` until it is `null`.

Watch for: there is no direction filter, so filter on `direction` yourself. Each document's amounts are in its own `currency`, so don't add across currencies.

### "Invoice our counterparty for two units at $95, 8% tax."

1. **Find the connection.** Documents carry `connection_id`, not counterparty names. Take the id from a document already on that relationship, or ask the person which connection to use.
2. **Create a draft.** Call `createDocument` with `idempotency_key: "invoice-<your ref>-create"` and this body:

   ```json
   {
     "connection_id": "…",
     "document_type": "invoice",
     "line_items": [{ "description": "…", "quantity": 2, "unit_price": 95, "tax_rate": 0.08 }],
     "external_refs": { "issuer_ref": "<your ref>" }
   }
   ```

   The draft is yours alone until you issue it: the counterparty can't read it and hears nothing about it. Still put nothing in it you wouldn't send.
3. **Confirm, then issue.** Show the person the draft's `number` and `total`. When they confirm, call `issueDocument` with `idempotency_key: "invoice-<your ref>-issue"`. The status becomes `submitted`, and the counterparty now has it as their Bill.

Watch for:
- Tax is per line (`tax_rate`); a document-level tax figure is refused.
- Rivet assigns `number`. Keep your own number in `external_refs.issuer_ref`.
- `forbidden` means your organization isn't a party to that connection.
- `conflict` with "no active workflow" means a person has to set up the connection's workflow in the app first.
- `conflict` saying the document is awaiting release approval means your organization's workflow sends it once its approvers sign off. Don't issue it again.

### "Approve that bill."

1. `getDocument`, to read its current `status`. Approving is the receiver's act, from `submitted`, `acknowledged` or `due`.
2. `transitionDocument` with `{"to": "approved"}` and an `idempotency_key`.
3. On `conflict`:
   - If `details.allowed` is present, it lists the transitions that are legal for your side right now. Use one only if it is what the person asked for; otherwise stop and report the list.
   - If the message says the document is awaiting workflow approval, the organization's approval workflow gates it, and the decision is made through the approval in the app. Stop and tell the person.

### "Pay that bill."

1. Confirm the person asked for this specific payment. The bill must be `approved` (or `overdue`), and the key needs `documents:write` and `payments:write`.
2. Call `payDocument` with `idempotency_key: "pay-<document id>-1"`. It pays the full balance due and returns the document as `payment_scheduled`.
3. Read the outcome later with `getDocument`, or from the `payment.settled` and `payment.failed` webhooks. The document is the payment's status.

Watch for:
- **Limits and switches.** `payment_limit_exceeded` names the limit. On a live organization, `not_enabled` or `capability_unavailable` means this deployment hasn't enabled writes or payments for live organizations yet.
- **Partial amounts** aren't accepted.
- **Replays.** The same `idempotency_key` returns the original response for 24 hours, never a second payment, even if that payment later failed. A new attempt after `payment.failed` needs a new key: `pay-<document id>-2`.
- **In a sandbox** the payment is simulated. The bill reads `payment_scheduled` until the supplier confirms the settlement: call `advanceSandboxCounterparty` with the bill's `document_id`. The bill becomes `paid` and `payment.settled` fires.

### "Pay this Rivet invoice in USDC."

Follow *Pay by agent* at `https://api.rivet.network/v1/docs/pay-by-agent.md`. For a bill your organization received, call `payDocument` with `body: {"payment_method": "usdc"}` for the terms, then again with the signed `payment` in the body: the key's `payment_limits` apply. On a live organization this needs writes for live organizations and pay by agent on (`families.pay` in `getEnablement`), not the fiat payments switch; `not_enabled` says which is off. An agent that holds only the invoice's pay reference uses `getPayChallenge` and `payWithX402`, with or without a Rivet key.

### "Keep our system in sync."

- **Polling.** Call `listDocuments` with `updated_since` set to the newest `updated_at` you have stored. Page to the end, then store the new newest value.
- **Webhooks.** A person registers an endpoint; it needs `keys:manage`. The events are `document.created`, `document.status_changed`, `document.edited`, `payment.settled`, `payment.failed` and the `finance.*` events.
  - Verify `Rivet-Signature` against the raw body.
  - Refuse timestamps more than five minutes off.
  - Dedupe on the event's `id` in the body, because delivery is at least once and a manual redelivery arrives under a new delivery id.
  - Your drafts send nothing to the counterparty. When you issue one, their endpoint receives `document.created` for their copy, and yours receives `document.status_changed`.
  - `data.document` is the document as `listDocuments` shows it, plus `pay_url`: on a received invoice whose payee accepts USDC from agents, the pay-by-agent link (null on the first delivery after the send, present from the next event on); null everywhere else.

### "Prove this invoice's history to our lender."

1. `getDocumentHistory` returns the history, verified on every read. `getDocumentLineage` shows where the invoice came from. Steps on the counterparty's own side appear as sealed hashes with no content.
2. `exportDocumentProvenance` returns a bundle. Its digest is the sha256 of the exact bytes returned; over HTTP it also arrives as `Rivet-Bundle-Digest`. Keep both. The lender verifies the bundle offline with the published script, without a Rivet account.
3. `getDocumentAttestation` returns a signed, identity-free summary. Anyone can check its digest at `/public/verify/{digest}`.

Watch for:
- These calls need `provenance:read`.
- Exports have their own small daily budget per key.
- An ancestry too large to export answers `conflict`: a bundle is complete or absent.

### "Put this page in my Rivet drive."

1. `createShareItem` with `{"kind": "html", "title": "Q3 board review", "html": "<!doctype html>…"}`: the whole page, standalone, up to 4 MB. A link is `{"kind": "link", "title": "…", "url": "https://…"}`.
2. Tell the person it's in their drive (`drive_url`), restricted until they share it. You never get its address, which holds its share token.
3. A page is `scanning` until its malware scan is clean. `getShareItem` with its `id` says when it's `ready`.

### "Practice the whole loop without real money."

1. **A person sets it up.** They provision the sandbox in **Settings → Developers**, or with `provisionSandbox` and a `keys:manage` key on the parent organization. Then they mint an `rk_test_` key on it with the scopes the agent needs, plus `keys:manage`.
2. **The sandbox has two demo counterparties.**
   - Riveted Demo Co sells to you, so its invoices are your Bills.
   - Riveted Demo Buyers buys from you, so your invoices to it are your receivables.
   - Each sits on its own connection. `listDocuments` shows the seeded documents and their `connection_id`.
3. **Drive both sides.**
   - **Receivables:** issue an invoice to Riveted Demo Buyers, then call `advanceSandboxCounterparty` with `{"document_id": "…"}`. Each call moves the buyer one step: it acknowledges, approves, then pays (simulated). The counterparty acts only when you call it.
   - **Bills:** for bills from Riveted Demo Co, you do the moving. Approve with `transitionDocument`, pay with `payDocument`, then call `advanceSandboxCounterparty`: the supplier confirms the settlement and the bill reads `paid`. Calling it before you have paid answers `conflict` and says the next move is yours.
4. **Reset from the parent.** When the document quota is spent you get `sandbox_quota`. A person resets the sandbox from the parent organization; the sandbox's own key can't.

## 6. Errors, and what to do next

Errors use one envelope: `{"error": {"code": "…", "message": "…", "docs_url": "…", "details": {…}}}`. The exception is a `402`, whose body is the x402 challenge (see *Pay by agent*).

| Code | HTTP | What the agent does |
|---|---|---|
| `invalid_request` | 400 | Fix the request; the message names the field. Unknown query parameters are refused. |
| `unauthorized` | 401 | Stop. The key is missing, unknown, revoked or expired. Tell the person. |
| `forbidden` | 403 | Stop. Your organization isn't a party to that connection, the call came from outside `allowed_cidrs`, or it needs an admin. |
| `insufficient_scope` | 403 | Stop. Tell the person which scope the key lacks. Don't ask for a broader key just to get past it. |
| `capability_unavailable` | 403 | Stop. The organization doesn't have this yet, for example because verification isn't finished or payments aren't enabled. |
| `payment_limit_exceeded` | 403 | Stop. A person set this limit. Never split a payment to fit under it. |
| `sandbox_isolation` | 403 | Stop. A test key was used on live data, or a live key on the sandbox. |
| `owner_not_verified` | 403 | Stop. Simulated Rivet Money turns on in a sandbox only while the organization that owns it is verified by Rivet. Tell the person. |
| `not_found` | 404 | It doesn't exist for your organization. Don't retry. |
| `conflict` | 409 | Read `details`. On transitions, `details.allowed` lists the legal moves; use one only if the person asked for it. |
| `idempotency_conflict` | 409 | The same key was sent with a different body. Use a new key only if this really is a new action. |
| `approval_required` | 409 | Stop. The payer's own workflow holds the invoice; a person approves it in the app. |
| `not_payable`, `financed_invoice_rivet_rail_only` | 409 | Stop. The invoice can't be paid this way. |
| `payment_amount_stale` | 409 | The balance changed. Read a fresh challenge, and pay only if the new amount is still what the person approved. |
| `payment_required` | 402 | Not a failure: these are the x402 terms. If the body carries `error`, your signature was refused. |
| `x402_settle_failed` | 502 | Nothing was booked. Send the same signed payload again later. Before signing a replacement, check on-chain that the old authorization was never used (see *Pay by agent*). |
| `rate_limited` | 429 | Wait for `Retry-After`. Over MCP you can't see that header, so back off a minute and then longer. |
| `sandbox_quota` | 429 | A person resets the sandbox from the parent organization. |
| `not_enabled` | 503 | This is off in this deployment, or for live organizations here. Say so; don't retry. |
| `service_unavailable` | 503 | Back off and retry. For a document write, keep the same `idempotency_key`; for `payWithX402`, keep the same signed payload. |
| `internal_error` | 500 | Retry once, as for `service_unavailable`. If it fails again, report it. |

## 7. Rules for an agent acting on a company's record

1. **Practice first.** Use an `rk_test_` key until the flow is right. On an `rk_live_` key you are acting for a real business.
2. **Every payment needs a person's request for that payment.** This covers `payDocument`, `transitionDocument` to `payment_scheduled` or `paid`, and `payWithX402`. Never repeat a payment, split one, or route around a refusal.
3. **Retry with the same identity.** For the five document writes and `createShareItem`, build the `idempotency_key` from the action and the attempt, and reuse it on a retry. For `payWithX402`, re-send the same signed payload. A fresh key or a fresh signature is a new payment attempt. Sign a replacement only after confirming on-chain that the old authorization was never used.
4. **Draft, show, then issue.** Show the person the draft before `issueDocument` submits it. Until then the counterparty can't see it.
5. **Some refusals belong to a person.** On `approval_required`, `payment_limit_exceeded`, `capability_unavailable` or `insufficient_scope`, the agent reports and stops.
6. **Quote your own side.** Use the `label` and `number` your organization sees.
7. **Keys stay out of prompts.** Load them from the environment or a secrets manager. Never write one into a document, a message or a log.

## Where everything is

- The contract (OpenAPI 3.1, no key needed): `https://api.rivet.network/v1/openapi.json`
- The interactive reference: `https://api.rivet.network/v1/docs`
- Every MCP tool: `https://api.rivet.network/v1/docs/mcp-tools.md`
- Paying a Rivet invoice by agent: `https://api.rivet.network/v1/docs/pay-by-agent.md`
- Which families are on here: `https://api.rivet.network/v1/enablement`
- The index of all of this, for agents: `https://api.rivet.network/llms.txt`
