4. Rate limits
Per token, sliding window, fail-open (a flaky limiter never takes the API down):
| Token type | Limit |
|---|---|
service |
10 000 / min |
human, break_glass |
1 000 / min |
Over the limit → 429 with a Retry-After header (seconds). Back off and retry.
Token types & expiry
Limits are enforced per token and per token type — the type also decides how mutations are trusted on the actions MCP surface:
UI: sign in to your team app (https://<your-team>.marduk.app) → Admin →
Settings → API Keys (/admin/settings/api-keys) → New token. Copy the
jst_… value once — it is shown only at creation and stored hashed.
Token types
| Type | Expiry | Trust (on /api/v1/mcp/actions) |
Use for |
|---|---|---|---|
service |
none (rotate manually) | trusted — mutations execute immediately | server-to-server integrations, your adjacent app's backend |
human |
1h access + 30d refresh | untrusted — mutations queue for human approval | a user acting through your app |
break_glass |
4h | untrusted | audited emergency access |
Scopes. A token carries a JSONB scope list ([{resource, actions[]}]).
Leaving it empty grants full team access (the wildcard *). Every v1
resource route declares its true resource (G-1 closed, mos-hq6q4.81), so a
least-privilege token works — e.g. forms:read can list forms but gets 403 on
tables. Scope resources include contacts, tables, forms, files,
artifacts, bases, review, workflows, wellness, extension, vault,
comms, emr, webhooks, audit, tokens, *; actions are
read / write / delete / admin. The MCP dispatch surface still requires
the wildcard — see agentic-rails.md §G-1.
Every request is Authorization: Bearer jst_….