Skip to main content
Reinx has three kinds of credentials, for three kinds of callers: The two API-key types are not interchangeable and never overlap. An agent key can spend but cannot create or reconfigure agents. An organization key can create and reconfigure agents but can never move money. That separation is structural, not a setting.
An organization key is recognised only on the management endpoints — the routes that compose requireOrgOrKey (agent read/write, transactions read, audit read, project/team create + budget + approval rules). It is not a general /v1/* credential: money-out, funding, bank-linking, and key-management routes require a human dashboard session and reject an org key as an invalid token. The exact list is the management endpoint matrix (and its REST equivalents); anything outside it is a 401, not a scope error.

Agent API keys

Keys are minted per agent, use the sk_reinx_live_ prefix, and are sent as a Bearer token:
  • Shown once at creation — only a hash is stored. If lost, revoke and mint a new one.
  • One active key per agent. Revocation propagates in seconds.
  • Keys can be scoped to a subset of MCP tools at creation.
  • An invalid or revoked key returns 401. Archiving an agent revokes its key, so an archived agent’s requests also return 401. A paused agent keeps its key and returns 403 AGENT_PAUSED.
The agent’s key is an access badge, not a wallet: agents never hold card numbers or wallet signing keys. Store the key in your agent’s runtime configuration, never in its prompt or memory.

Organization API keys

An organization key is a machine principal — it has no human identity behind it. Mint one in Settings → Organization → API Keys (minting requires step-up re-verification; that ceremony is what authorizes the key, so requests carrying it are never re-challenged).
Two independent things narrow what a key can do. Both must pass on every request.

1. Scopes — what kind of action

Six scopes are grantable at mint:
Omitting scopes at mint yields a read-only key — the three reads above. Every write scope must be selected affirmatively; there is no “grant everything” default, and a key minted before write scopes existed stays read-only. Note the asymmetry: omitting scopes gives you the default set, but sending an empty array is an error (400 EMPTY_SCOPES), as is requesting anything outside the six (400 INVALID_SCOPE — a few scope strings exist in the vocabulary but are deliberately not grantable).
Write scopes cover their own preconditions. These reads require the write scope, not the matching read scope, because each is a write-precondition — it returns the version token the follow-up write needs (a full-replacement PUT for the project/team envelopes; a partial PATCH for the agent budget-policy, which leaves omitted fields unchanged):
  • GET /v1/projects/:id/budget and /approval-rulesprojects:write
  • GET /v1/teams/:id/budget and /approval-rulesteams:write
  • GET /v1/agents/:id/budget-policyagents:write
So an agents:write key is self-sufficient for the read-modify-write loop; it does not also need agents:read.

2. Binding — where the action may land

Every key is bound to a container at mint: the organization, a single project, or a single team. The binding filters every read and gates every write. A binding never reaches the route that edits its own container’s envelope. A project-bound key cannot raise its own project’s budget cap, and a team-bound key cannot raise its own team’s. That route lives one rung up, by design — a key must not be able to widen the ceiling it operates under. Attempting it returns 403 BINDING_NOT_ALLOWED. Team-bound keys cannot create agents because human Team Leads cannot either; a machine never exceeds the human rung it mirrors.
Read the full model — including everything an organization key can never do regardless of scopes — on Organization key limits and boundaries.

Base URL

All endpoints are versioned under /v1/. Breaking changes will be introduced under /v2/ without disrupting existing integrations.

Rate limits

Per-organization request ceiling, by plan: MCP has its own, lower ceiling — 60 / 600 / 3,000 per minute for Free / Pro / Enterprise. No single field is present on every 429 — not retry_after_seconds, not retry-after, not even a stable error code. Which limiter tripped decides the shape, so branch on the source and treat every field as optional:
The last row is not an HTTP 429. The other MCP rows (gateway L2, pre-auth IP) reject at the transport and return an actual HTTP 429. But when a management tool’s downstream REST call is throttled, the tool has already dispatched — the proxy wraps the upstream limit as an MCP tool result (isError: true) and the outer JSON-RPC HTTP response stays 200. So for management tools you cannot detect a throttle from the HTTP status; parse the relayed error / retry_after_seconds out of the tool-result body instead.
Two REST 429s carry no delay field, and their x-ratelimit-* headers lie — they need opposite handling. When an upstream provider rate-limits an onboarding, funding, or bank-linking call, Reinx relays 429 RATE_LIMITED with no retry_after_seconds and no retry-after. It is not header-free, though: the per-organization limiter runs as a pre-handler and already stamped x-ratelimit-* for the general bucket, which never tripped — so those headers look healthy and tell you nothing about the upstream limit that actually rejected you. Ignore them and back off on your own schedule (start around a second and grow it); retrying immediately just re-hits the upstream. The other, CREATE_QUOTA_EXHAUSTED, likewise carries stale general-bucket headers but must not be retried on a backoff at all — it clears only at 00:00 UTC. Branch on the error code, never on the presence of x-ratelimit-*.
The REST pre-auth IP limiter does not use the Reinx error envelope. It returns the framework default — { "statusCode": 429, "error": "Too Many Requests", "message": "…" } — where error is a human string, not a machine code. A parser keyed on error === "RATE_LIMITED" will not recognise it, and one that treats error as a stable code will read "Too Many Requests" as if it were one. Detect rate limiting from the 429 status, not from the body.
x-ratelimit-reset is seconds remaining on REST and a Unix epoch on MCP.
On a REST success, the headers describe the general per-organization bucket — so they can look healthy while a tighter limit is about to reject you. They only switch to the bucket that actually tripped on a 429. Two things are therefore invisible on a successful response: per-route caps (e.g. POST /v1/agents is 10/min per organization) and the daily creation quota below. MCP adds management-write caps (20/min per key, 40/min per organization) on top, likewise unreported.

Daily creation quota

Every organization key carries a daily quota on resource creation — agents, projects, and teams share one counter. Default 100/day, resetting at 00:00 UTC (not on a rolling window).
  • Exhaustion returns 429 CREATE_QUOTA_EXHAUSTEDwith no retry-after header and no retry_after_seconds, because there is no short window to wait out. Backing off will not clear it; it clears at 00:00 UTC. Do not feed this 429 into a generic rate-limit retry loop.
  • If the quota store is unreachable you get 503 QUOTA_UNAVAILABLE — safe to retry. The quota is deliberately fail-closed, so it denies rather than admits.
  • The quota is charged per admitted attempt, not per success, and is never refunded. A create that passes the quota and then fails during provisioning has still consumed a unit. Retries within the same UTC day are never re-charged — same key, same payload, complete or not, the attempt marker dedupes it. The one case that costs another unit is resuming an incomplete attempt after 00:00 UTC, because the marker is day-scoped.
  • Rotating a key does not reset the counter — it follows the key’s lineage.

Errors

Errors use a flat envelope:
In this normal Reinx envelope, error is a stable machine-readable code and message is human-readable and may change — branch on error. Two responses do not follow it (both noted above): the REST pre-auth IP limiter’s 429 puts the human string "Too Many Requests" in error, and request-validation failures put the stable code in code (with error set to "Bad Request"). So detect rate limiting from the 429 status, and read validation codes from code — never assume error is a stable code on those two. See the error reference for the full list, including the organization-key codes (INSUFFICIENT_SCOPE, BINDING_NOT_ALLOWED, VERSION_CONFLICT, and others).