API guides

Authentication

Keys, scopes, and what happens when you revoke one.

One header, on every call. There is no other way in, and no unauthenticated read except the specification itself.

An authenticated call
From the live spec
curl "https://api.rivet.network/v1/documents" \
  -H "Authorization: Bearer rk_live_…"

What a key is

A key has two halves. The prefix — the first characters, like rk_live_Kq3Z — is stored and shown forever, so you can recognize a key in a list. The secret is shown exactly once, in the response that creates it; Rivet keeps only its hash. If it is lost, it cannot be recovered: mint another and revoke the old one.

Scopes

A key carries the scopes it was granted at creation. Ask for what the integration needs and nothing more — you can always mint a second key.

documents:readDocuments family reads
documents:writecreate, edit (drafts), issue and transition documents
payments:readPayments family
payments:writeexecute 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:readTreasury family reads (verified organizations only; never in sandbox)
provenance:readverified history, lineage and the export bundle (your own documents; sandbox included)
finance:readSee the reference.
finance:writeSee the reference.
keys:managekey and sandbox management (admin scope; grantable only from an admin session)
share:writeadd 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)

A key granted payments:write can also carry payment_limits — max_per_payment and daily_volume, set at mint and visible on the key resource. They are governors on what the key may schedule, enforced before anything moves.

A scope never exceeds what your organization itself has. The two are intersected on every call: a key with treasury:read in an organization that isn’t money ready yet is refused with capability_unavailable, not quietly given an empty list. The scope says what the key may ask for; the organization decides what exists to answer with.

Expiry and allowed ranges

Two more governors are set at mint and shown on the key resource. expires_at is an instant after which the key is refused with unauthorized — the same answer as a revoked key, so nothing downstream has to tell them apart. Leave it out for a key that does not expire. allowed_cidrs is a list of up to twenty IPv4 or IPv6 ranges; a call from any other address is refused with forbidden before the request is looked at, even with a valid key. Leave it out to allow any address. Both are readable on every key so an audit can see which keys are open-ended.

A governed key
From the published contract
curl -X POST "https://api.rivet.network/v1/keys" \
  -H "Authorization: Bearer <your admin session token>" \
  -H "Content-Type: application/json" \
  -d '{"name":"Warehouse sync","scopes":["documents:read"],
       "expires_at":"2027-03-31T00:00:00Z",
       "allowed_cidrs":["203.0.113.0/24","2001:db8::/32"]}'

Rotation

Rotating mints a replacement with the same name, scopes and limits, and gives the old key 24 hours of grace: both work until then, so the integration can be moved over without downtime, and the old key expires on its own — or revoke it early once the switch is confirmed. The new secret is in the response exactly once; the old key’s record shows rotated_to_id and its new expires_at, and the new key’s shows rotated_from_id, so the lineage is visible on both.

Rotate a key
From the live spec
curl -X POST "https://api.rivet.network/v1/keys/{keyId}/rotate" \
  -H "Authorization: Bearer rk_live_…"
The response
From the published contract
{
  "id": "6a5eed00000000000000ee02",
  "name": "Warehouse sync",
  "prefix": "rk_live_Xt7P",
  "key": "rk_live_Xt7P…",              // shown exactly once
  "scopes": ["documents:read"],
  "expires_at": null,
  "allowed_cidrs": ["203.0.113.0/24"],
  "rotated_from_id": "6a5eed00000000000000ee01",
  "previous": {
    "id": "6a5eed00000000000000ee01",
    "prefix": "rk_live_Kq3Z",
    "expires_at": "2026-09-11T12:00:00Z",   // 24 hours of grace, then refused
    "rotated_to_id": "6a5eed00000000000000ee02"
  }
}

Revocation

Revoking is immediate and permanent. The next request with that key is refused with unauthorized — there is no grace window and no re-enabling. Revoking an already-revoked key changes nothing and returns the same record, so a retry is safe.

Three practices

  • Never in client code. A key acts as your whole organization. It belongs on a server or in a secrets manager — never in a browser bundle, a mobile app, or a repository.
  • Rotate on a schedule. A rotation keeps the old key working for 24 hours while you move the integration over — no downtime, and no window where a lost secret keeps working forever. Give long-lived keys an expires_at so the schedule enforces itself.
  • One key per integration. Separate keys mean you can revoke the warehouse sync without taking down the billing job — and last_used_at tells you which is which.