The Cloud API is the HTTP JSON API behind the dashboard. Everything you do in the dashboard (creating agents, editing policies, deciding approvals, reading receipts) is a Cloud API request. You can also call it yourself, with a signed-in session or with a management API key, to automate setup and read results.
This page explains the concepts every request shares and lists the route families with the permission each needs. It is not a full schema of every request and response body.
Base URL and request format
All routes live under the base path /api/v1 of the Cloud API. The dashboard calls the same API on its own address, for example https://cloud.example.com/api/v1/.... A script calls the Cloud API at the base URL that K-Block gives you, for example https://api.example.com/api/v1/... for the base URL https://api.example.com. If you do not have the Cloud API's base URL, contact K-Block support.
| Rule | Behaviour |
|---|---|
| Body format | JSON only. Another Content-Type answers 415 UNSUPPORTED_MEDIA_TYPE. |
| Body size | At most 1 MiB; larger bodies answer 413 PAYLOAD_TOO_LARGE. |
| Unknown fields | Bodies and query strings are strict: an unknown member answers 400 VALIDATION_FAILED. |
| Malformed IDs | An ID in the path that is not well formed answers 404, never 400. |
| Caching | Every response carries Cache-Control: no-store unless a route sets its own. |
| Request IDs | Every response carries X-Request-Id: yours if you sent a well-formed one, otherwise a new one. Quote it when you report a problem. |
| Cross-origin | No CORS headers are sent. Browsers use the API only through the dashboard's origin. |
Authentication
A request is authenticated in one of two ways: a person's session cookie, or a management API key. Some routes accept only one of them.
Sessions (people)
A person signs in with POST /api/v1/auth/signin and an e-mail address and password. The answer sets the kb_session cookie and contains the session, including a csrf_token. GET /api/v1/auth/session returns the same token later.
-
The cookie is
HttpOnly,SameSite=LaxandSecure. -
A session ends after 12 hours without activity, and 7 days after sign-in at the latest.
-
Every request with a session and an unsafe method (
POST,PUT,PATCH,DELETE) must carry both:- an
Originheader equal to the dashboard's own origin, the address you open the dashboard at (for examplehttps://cloud.example.com), and - the header
X-KBlock-CSRFwith the session'scsrf_token.
Otherwise it answers
403 CSRF_FAILED. - an
-
Sign-in, sign-up and the other unauthenticated
POSTroutes refuse a foreignOriginheader with403 CSRF_FAILED. A request withoutOrigin(a non-browser client) passes. -
An account must have a verified e-mail address to sign in; an unverified one gets the same
401 INVALID_CREDENTIALSas a wrong password. A session whose account has no verified address (for example, one created before this rule) gets403 EMAIL_NOT_VERIFIEDeverywhere except the routes that serve verification, e-mail change, sign-out, the session itself and password changes.
The dashboard does all of this for you. Sessions are what people use; for automation, use a management API key.
Management API keys (automation)
A management API key authenticates a script or service on behalf of an organization. Send it as a bearer token to the Cloud API's own address:
GET /api/v1/orgs/<org-id>/agents HTTP/1.1
Host: api.example.com
Authorization: Bearer kb_live_…- Only owners and admins create management keys, in the dashboard (see API keys). The full key is shown once.
- Each key has one or more scopes:
read,agents:write,tools:write,policies:write,approvals:read,receipts:read. - A key belongs to one organization. A request for another organization's ID answers
404. - A key without the scope a route needs answers
403 FORBIDDENnaming the missing scope. - Many routes never accept keys, for example deciding approvals, members, invitations, API keys, the audit log, webhooks, organization settings, policy activation and rollback, emergency stop, delegations, cancellations and billing checkout. A key there answers
403 FORBIDDEN. These need a signed-in person. The tables in Route families mark every one. - Requests with a key need no CSRF token; they carry no cookies.
- Gateway API keys (the ones agents use) are not accepted by the Cloud API: on routes that take management keys they answer
401 UNAUTHENTICATED. On routes that never take keys, any bearer token answers403 FORBIDDENbefore it is checked. - A key stops working when it is revoked or expires, and also when the member who created it leaves the organization, is disabled, or loses the role needed to create that kind of key.
Roles and permissions
Every route under /orgs/<org-id> checks the caller against the organization in the path, never against the organization selected in the dashboard. For a session, the member's role is read again on each request. A non-member gets 404; a member whose role is not enough gets 403 FORBIDDEN.
| Permission | Roles | Management key scope |
|---|---|---|
| Read organization data | owner, admin, approver, developer, viewer | read (approvals: approvals:read; receipts: receipts:read) |
| Manage resources (agents, MCP servers, tools, policy drafts, gateway keys, webhooks) | owner, admin, developer | agents:write, tools:write or policies:write, on the routes that accept keys |
| Decide approvals | owner, admin, approver | never |
| Administer (activate policies, members, invitations, management keys, settings, delegations, cancellations) | owner, admin | never |
| Read the audit log | owner, admin | never |
| Owner actions (billing checkout and portal, owner changes, organization principal) | owner | never |
Some writes need more than the route's permission. For example, a developer can create a tool, but only an owner or admin can enable it. Those cases are noted in the tables below and explained on Roles and permissions.
Pagination
List routes return one page at a time (a few short, fixed lists, such as the policy templates, return all items at once):
{ "items": [], "next_cursor": null }?limit=sets the page size, from 1 to 100. The default is 50.next_cursorisnullon the last page. Otherwise pass it as?cursor=to get the next page.- Cursors are opaque. Do not build or edit them.
Errors
Every error has the same JSON body:
{
"error": {
"code": "VALIDATION_FAILED",
"message": "invalid request body",
"details": { "location": "body", "issues": [] }
}
}code is stable and machine-readable; message is for people and may change. details is present only for some codes, for example issues for validation errors (each with path, code and message, never the submitted value) or retry_after_seconds for rate limits. A 500 INTERNAL_ERROR never includes details of what failed.
| Status | Common codes | What to do |
|---|---|---|
| 400 | VALIDATION_FAILED, INVALID_JSON, INVALID_INPUT | Fix the request; the issues name the fields. |
| 401 | UNAUTHENTICATED, INVALID_CREDENTIALS | Sign in again or check the API key. |
| 402 | PLAN_LIMIT_EXCEEDED | The organization's plan limit is reached. See Billing. |
| 403 | FORBIDDEN, ADMIN_REQUIRED, OWNER_REQUIRED, CSRF_FAILED, EMAIL_NOT_VERIFIED | A role, scope, CSRF token or verified address is missing. |
| 404 | NOT_FOUND | The resource does not exist, or you cannot see it. |
| 409 | CONFLICT, NAME_TAKEN and others | The state changed or the name is in use; reload and retry. |
| 422 | VERIFICATION_FAILED, POLICY_NOT_FOUND and others | A signed object in the body did not verify, or the body names something that does not exist in the organization. |
| 429 | RATE_LIMITED, DISCOVERY_BUSY | Wait for Retry-After seconds. |
| 500 | INTERNAL_ERROR | An unexpected error on K-Block's side. Contact K-Block support with the code and the request ID. |
| 503 | SERVICE_UNAVAILABLE | K-Block is temporarily unavailable or busy; retry later, after Retry-After (5 seconds) when the answer carries it. If it continues, contact K-Block support with the code and the request ID. |
A 5xx answer with a code that Error codes does not list, and a 503 without Retry-After, mean a problem on K-Block's side, for example when K-Block cannot send e-mail right now or plans cannot be bought or managed here (see Server-side problems). Their codes are for K-Block's own diagnosis. Retry later; if the problem continues, contact K-Block support with the request ID from the X-Request-Id response header.
The full list with meanings is on Error codes.
Rate limits
Every route under /api/v1 is rate limited per client IP address (1,200 requests per minute) and, when the request carries a session or an API key, per credential (600 requests per minute). Design your scripts for these numbers.
Sensitive routes have their own, lower limits, for example sign-in, sign-up, password reset, invitations, webhook tests, dry runs and policy analysis. The numbers are on Limits and supported versions.
A refused request answers 429 RATE_LIMITED with a Retry-After header and details.retry_after_seconds. Nothing was changed; repeat the same request after the wait.
Idempotency
The Cloud API has no Idempotency-Key header, and it does not deduplicate repeated writes. If a create request times out, list the resources before you retry it: a repeated create can create a second resource, or fail with a uniqueness error such as 409 NAME_TAKEN.
Idempotency keys exist for agent tool calls at the Gateway, where a call can carry kblock/idempotency-key. See Actions.
Route families
Paths below are relative to /api/v1. "Members" means every role; "Writers" means owner, admin and developer; "Approvers" means owner, admin and approver; "Admins" means owner and admin. "Key" names the scope a management key needs, or "no" when keys are refused.
Account and session
These routes act on the signed-in person, not on an organization.
| Method and path | Who | Key |
|---|---|---|
POST /auth/signup | Anyone (202 whether or not the address already has an account) | no |
POST /auth/signin | Anyone | no |
POST /auth/signout, GET /auth/session | Signed-in person | no |
POST /auth/password | Signed-in person (current password required) | no |
POST /auth/email/verify, POST /auth/email/resend | Link holder / anyone | no |
POST /auth/email/change/approve, POST /auth/email/change/reject | Link holder | no |
POST /me/email | Signed-in person (password required) | no |
POST /auth/password-reset, POST /auth/password-reset/confirm | Anyone / link holder | no |
POST /me/identity-challenges, GET /me/identities, POST /me/identities, DELETE /me/identities/<kid> | Signed-in person | no |
POST /invitations/accept | Signed-in invitee whose verified address matches | no |
GET /gateway/identity | Any signed-in person or management key | any scope |
Organizations, members and invitations
| Method and path | Who | Key |
|---|---|---|
POST /orgs | Any signed-in person with a verified address (becomes owner) | no |
GET /orgs/<org-id> | Members | read |
PATCH /orgs/<org-id> | Admins; the observe settings: owner only | no |
POST /orgs/<org-id>/switch | Members | no |
GET /orgs/<org-id>/members | Members | no |
PATCH /orgs/<org-id>/members/<user-id> | Admins; granting or changing owner: owner only | no |
DELETE /orgs/<org-id>/members/<user-id> | Yourself; others: admins; an owner: owner only | no |
GET, POST /orgs/<org-id>/invitations; DELETE …/invitations/<id> | Admins | no |
GET, POST /orgs/<org-id>/principal | Members read; owner sets | read (GET only) |
API keys and audit log
| Method and path | Who | Key |
|---|---|---|
GET /orgs/<org-id>/api-keys | Writers; developers see gateway keys only | no |
POST /orgs/<org-id>/api-keys | Gateway keys: writers; management keys: admins | no |
POST /orgs/<org-id>/api-keys/<key-id>/revoke | Same as creating that kind | no |
GET /orgs/<org-id>/audit | Admins | no |
Agents, delegations and observe windows
| Method and path | Who | Key |
|---|---|---|
GET /orgs/<org-id>/agents, GET …/agents/<id> | Members | read |
POST /orgs/<org-id>/agents | Writers | agents:write |
PATCH /orgs/<org-id>/agents/<id> | Writers; switching the policy: admins, signed in | agents:write |
GET …/agents/<id>/delegations | Members | read |
POST …/agents/<id>/delegations, POST /orgs/<org-id>/delegations/<id>/revoke | Admins | no |
POST …/agents/<id>/dry-run | Members | read |
GET …/agents/<id>/observe, GET /orgs/<org-id>/observe-windows, GET …/observe-windows/<id>, GET …/observe-windows/<id>/report | Members | read |
POST …/agents/<id>/observe | Admins | no |
POST …/agents/<id>/observe/end, POST /orgs/<org-id>/observe/end-all | Writers | agents:write |
MCP servers and tools
| Method and path | Who | Key |
|---|---|---|
GET /orgs/<org-id>/mcp-servers, GET …/mcp-servers/<id> | Members | read |
POST /orgs/<org-id>/mcp-servers | Writers | tools:write |
PATCH …/mcp-servers/<id> | Writers; changing what an enabled tool is bound to: admins, signed in | tools:write |
DELETE …/mcp-servers/<id>, POST …/mcp-servers/<id>/discover | Writers | tools:write |
GET /orgs/<org-id>/tools, GET …/tools/<id> | Members | read |
POST /orgs/<org-id>/tools, PATCH …/tools/<id> | Writers; enabling a tool or changing what an enabled tool means: admins, signed in | tools:write |
POST …/tools/<id>/authorize | Admins, signed in | no (a key with tools:write passes the scope check, then gets 403 ADMIN_REQUIRED) |
Policies
| Method and path | Who | Key |
|---|---|---|
GET /orgs/<org-id>/policies, GET …/policies/<id>, GET /orgs/<org-id>/policy-templates, GET /orgs/<org-id>/policy-status | Members | read |
POST /orgs/<org-id>/policies/validate | Members | read |
POST /orgs/<org-id>/policies, POST …/policies/from-template, POST …/policies/<id>/versions | Writers | policies:write |
POST …/policies/<id>/versions/<version-id>/activate | Admins | no |
POST …/policies/<id>/rollback, POST …/policies/<id>/emergency, POST …/policies/<id>/supersede-open-actions | Admins | no |
PATCH …/policies/<id> (agent disclosure) | Admins | no |
PUT, DELETE …/policies/<id>/shadow; PUT …/policies/<id>/examples | Writers | policies:write |
POST …/policies/<id>/simulate; GET …/simulations, GET …/simulations/<id>, GET …/shadow/report, GET …/versions/<version-id>/decision-map, GET …/diff | Members | read |
POST …/policies/<id>/simulations | Writers | policies:write |
GET …/policies/<id>/evidence | Admins | no |
Approvals, actions and receipts
| Method and path | Who | Key |
|---|---|---|
GET /orgs/<org-id>/approvals, GET …/approvals/<id> | Members | approvals:read |
POST …/approvals/<id>/decision | Approvers listed for that request | no |
GET /orgs/<org-id>/actions, GET …/actions/<id> | Members | read |
POST …/actions/<id>/cancel | Admins | no |
GET /orgs/<org-id>/receipts, GET …/receipts/<id>, GET …/receipts/<id>/decision-check, GET …/receipts/<id>/bundle | Members | receipts:read |
An approval decision carries an object signed in the person's browser with their personal K-Block key. A cancellation carries one signed by the organization principal, the identity set with POST /orgs/<org-id>/principal. The dashboard creates both. See How approvals work.
Overview, webhooks and billing
| Method and path | Who | Key |
|---|---|---|
GET /orgs/<org-id>/overview, GET /orgs/<org-id>/usage, GET /orgs/<org-id>/billing | Members | read |
GET, POST /orgs/<org-id>/webhooks; GET, PATCH, DELETE …/webhooks/<id> | Writers | no |
GET …/webhooks/<id>/deliveries, POST …/deliveries/<id>/redeliver, POST …/webhooks/<id>/test | Writers | no |
POST …/billing/checkout, POST …/billing/portal | Owner | no |
The Cloud API also has a route that receives events from Creem, K-Block's billing provider. Only Creem calls it; it is not for your scripts.
What to do next
- Create a management key with the scopes you need: API keys.
- Look up a specific error: Error codes.
- Check the rate limits and size limits: Limits and supported versions.