Orgs
20 operations under /v1/orgs/{orgId} (POST GET PATCH): parameters, status codes, refusal bodies, and the same request in curl, HTTP, Python and TypeScript.
Base URL: https://api.codespar.dev
Every operation below requires a Bearer token. See Authentication.
Four families share this prefix. Most paths here are legacy aliases the descriptions point away from. The four mandate operations are not aliases: they read the org-side mandate table, the one POST /v1/wallets/{id}/execute charges on account wallets, which is a different table from the consumer mandates under /v1/mandates. Issuance is off by default (the 501 is the expected answer on a default deployment). How the two mandate families differ, who signs each, and where each is exposed per channel are on the concept page, Mandates, receipts and approvals.
Every operation, from the spec
Generated from the published OpenAPI document, so it never drifts from what the API actually serves. The section above is written by hand and carries what a schema cannot: the object model, field rules, and the order to call things in.
POST /v1/orgs/{orgId}/agents
https://api.codespar.dev/v1/orgs/{orgId}/agentsRegister an agent identity and mint its first signing key
DEPRECATED alias of POST /v1/agents (ent#979), kept for two releases. Same handler, same agents:write scope; the canonical path drops the {orgId} segment because the org is always the authenticated one.
One call does both: it derives a did:web from the org and the handle, creates the agent, and mints key number one. The public key comes back base64; the private half never leaves the vault and is not returned here or anywhere else.
principal_ref is an OPAQUE KYC handle, and the check that keeps it opaque counts DIGITS, not digit runs. Every non-digit is removed from the whole value first, and what remains is refused if it is eleven characters or longer — so the rule is 'eleven or more digits anywhere in the value', and a handle like acct-1-2-3-4-5-6-7-8-9-0-1, which shows no run of digits at all, is refused. The reason for the rule is that this value is signed into the mandate presentation token and served to counterparties without authentication, so a CPF or CNPJ must never reach it. It is also not proof of KYC; nothing on this route verifies anything.
Registering an agent that already holds an active key is 409 agent_already_registered. 409 agent_did_owned_elsewhere means the derived did:web is held by another org's row, which is a caller-state conflict, not a server fault: register under a different handle.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
orgId | string | yes | — |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
agent_id | string | yes | — |
display_name | string | no | — |
principal_ref | string | yes | — |
role | string | no | — |
runtime | object | no | — |
Responses
| Status | Body | Description |
|---|---|---|
201 | object | OK |
400 | object | The body failed the schema (details.issues carries the Zod issues), or the org or the handle contains characters a did:web cannot hold. |
403 | object | The {orgId} in the path is not the authenticated org. |
409 | object | The agent already has an active key, or the derived did:web belongs to another org. |
Response 201
| Field | Type | Required | Description |
|---|---|---|---|
agent_did | string | yes | did:web: derived from the org and the handle. |
kid | string | yes | <agent_did>#<namespace>-<n> — the namespace names the deployment that minted the key (ent#1641). |
pubkey | string | yes | Raw 32-byte Ed25519 public key, base64. |
status | "active" | yes | — |
curl -X POST https://api.codespar.dev/v1/orgs/{orgId}/agents \
-H "Authorization: Bearer $CODESPAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "agt_0000000000000000",
"display_name": "Example",
"principal_ref": "string",
"runtime": {
"kind": "sdk",
"detail": "string"
},
"role": "string"
}'POST /v1/orgs/{orgId}/agents HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json
{
"agent_id": "agt_0000000000000000",
"display_name": "Example",
"principal_ref": "string",
"runtime": {
"kind": "sdk",
"detail": "string"
},
"role": "string"
}import os
import requests
res = requests.post(
"https://api.codespar.dev/v1/orgs/{orgId}/agents",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
json={
"agent_id": "agt_0000000000000000",
"display_name": "Example",
"principal_ref": "string",
"runtime": {
"kind": "sdk",
"detail": "string"
},
"role": "string"
},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/orgs/{orgId}/agents", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"agent_id": "agt_0000000000000000",
"display_name": "Example",
"principal_ref": "string",
"runtime": {
"kind": "sdk",
"detail": "string"
},
"role": "string"
}),
});
const data = await res.json();const r = await cs.api.response("post", "/v1/orgs/{orgId}/agents", {
path: {
orgId: "org_0000000000000000"
},
body: {
agent_id: "agt_0000000000000000",
display_name: "Example",
principal_ref: "string",
runtime: {
kind: "sdk",
detail: "string"
},
role: "string"
}
});
// r.status is one of the documented statuses (200, 403),
// each with its own body shape in r.data; nothing here throws on 403.
if (r.ok) {
console.log(r.data);
}{
"agent_did": "did:web:codespar.dev:org:agent",
"kid": "string",
"pubkey": "string",
"status": "active"
}POST /v1/orgs/{orgId}/agents/{agentId}/keys/rotate
https://api.codespar.dev/v1/orgs/{orgId}/agents/{agentId}/keys/rotateRetire the agent's active key and mint a fresh one
DEPRECATED alias of POST /v1/agents/{agentId}/keys/rotate (ent#979), kept for two releases. Same handler, same agents:write scope.
Takes no body. {agentId} is the HANDLE the agent was registered under, not its did:web; the DID is derived from the org and the handle the same way registration derived it.
ROTATION IS NOT REVOCATION. The retire and the mint share one transaction, and the retired key still verifies everything it signed BEFORE retirement, so no previously issued mandate is invalidated. It simply cannot sign anything new. To actually invalidate a key, use the revoke route below.
409 no_active_key means the agent exists but holds nothing to rotate, which is the state an agent row sits in before its first key.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
agentId | string | yes | The registration handle, not the did:web. |
orgId | string | yes | — |
Responses
| Status | Body | Description |
|---|---|---|
201 | object | OK |
403 | object | The {orgId} in the path is not the authenticated org. |
404 | object | No agent with that handle in this org. |
409 | object | The agent holds no active key to rotate. |
Response 201
| Field | Type | Required | Description |
|---|---|---|---|
agent_did | string | yes | — |
kid | string | yes | The new active key. |
pubkey | string | yes | Raw 32-byte Ed25519 public key, base64. |
retired_kid | string | yes | The key just retired. It still verifies what it signed before retirement; it can only no longer sign anything new. |
status | "active" | yes | — |
curl -X POST https://api.codespar.dev/v1/orgs/{orgId}/agents/{agentId}/keys/rotate \
-H "Authorization: Bearer $CODESPAR_API_KEY"POST /v1/orgs/{orgId}/agents/{agentId}/keys/rotate HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.post(
"https://api.codespar.dev/v1/orgs/{orgId}/agents/{agentId}/keys/rotate",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/orgs/{orgId}/agents/{agentId}/keys/rotate", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const r = await cs.api.response("post", "/v1/orgs/{orgId}/agents/{agentId}/keys/rotate", {
path: {
orgId: "org_0000000000000000",
agentId: "agt_0000000000000000"
}
});
// r.status is one of the documented statuses (200, 403),
// each with its own body shape in r.data; nothing here throws on 403.
if (r.ok) {
console.log(r.data);
}{
"agent_did": "did:web:codespar.dev:org:agent",
"kid": "string",
"pubkey": "string",
"retired_kid": "string",
"status": "active"
}POST /v1/orgs/{orgId}/agents/{agentId}/keys/{kid}/revoke
https://api.codespar.dev/v1/orgs/{orgId}/agents/{agentId}/keys/{kid}/revokeHard-revoke one of an agent's signing keys
DEPRECATED alias of POST /v1/agents/{agentId}/keys/{kid}/revoke (ent#979), kept for two releases. Same handler, same agents:write scope.
Takes no body. Unlike rotation, this is the verb that stops a key from counting: the mandate spend gate resolves the key an existing mandate was signed with and refuses the spend once that key is revoked, so revoking reaches BACKWARDS into mandates already issued.
{kid} is a full key id of the form <did:web:...>#<namespace>-<n>, and its # MUST be percent-encoded as %23 to survive as one path segment. This revokes ONE key; it does not revoke the agent identity.
Revoking an already-revoked key is 409 rather than a no-op, so a retry after a timeout is distinguishable from a first call.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
agentId | string | yes | The registration handle, not the did:web. |
kid | string | yes | <did:web:...>#<namespace>-<n>, with the # percent-encoded as %23. |
orgId | string | yes | — |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
403 | object | The {orgId} in the path is not the authenticated org. |
404 | object | No such key for that agent in this org. |
409 | object | That key is already revoked. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
kid | string | yes | — |
revoked_at | string (date-time) | yes | — |
status | "revoked" | yes | — |
curl -X POST https://api.codespar.dev/v1/orgs/{orgId}/agents/{agentId}/keys/{kid}/revoke \
-H "Authorization: Bearer $CODESPAR_API_KEY"POST /v1/orgs/{orgId}/agents/{agentId}/keys/{kid}/revoke HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.post(
"https://api.codespar.dev/v1/orgs/{orgId}/agents/{agentId}/keys/{kid}/revoke",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/orgs/{orgId}/agents/{agentId}/keys/{kid}/revoke", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const r = await cs.api.response("post", "/v1/orgs/{orgId}/agents/{agentId}/keys/{kid}/revoke", {
path: {
orgId: "org_0000000000000000",
agentId: "agt_0000000000000000",
kid: "string"
}
});
// r.status is one of the documented statuses (200, 403),
// each with its own body shape in r.data; nothing here throws on 403.
if (r.ok) {
console.log(r.data);
}{
"kid": "string",
"status": "revoked",
"revoked_at": "2026-01-15T12:00:00.000Z"
}GET /v1/orgs/{orgId}/approvals
https://api.codespar.dev/v1/orgs/{orgId}/approvalsList approvals held for review (deprecated path)
DEPRECATED alias of GET /v1/approvals (ent#979), kept for two releases. Same handler, same scope; switch the path and nothing else changes.
Without status you get only the pending ones, not all of them. Ask for status=approved,denied to see what has already been decided. The response is an array, with no envelope and no cursor. An {orgId} that is not the credential's own is 404 before any read.
An API key or an OAuth token lists only its own project's holds, without tool_input; the dashboard (service auth) lists the whole organization's, with it.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
orgId | string | yes | — |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
limit | integer,null | no | — |
status | string | no | Comma-separated; the default is pending. |
Responses
| Status | Body | Description |
|---|---|---|
200 | array of object | OK |
400 | object | The query did not match the schema. Bare body. |
404 | object | Not Found |
curl -X GET https://api.codespar.dev/v1/orgs/{orgId}/approvals \
-H "Authorization: Bearer $CODESPAR_API_KEY"GET /v1/orgs/{orgId}/approvals HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.get(
"https://api.codespar.dev/v1/orgs/{orgId}/approvals",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/orgs/{orgId}/approvals", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const result = await cs.api.get("/v1/orgs/{orgId}/approvals", {
path: {
orgId: "org_0000000000000000"
}
});[
{
"id": "obj_0000000000000000",
"org_id": "org_0000000000000000",
"project_id": "prj_0000000000000000",
"session_id": "ses_0000000000000000",
"agent_id": "agt_0000000000000000",
"origin": {
"kind": "agent"
},
"matched_rule_id": "matchedrule_0000000000000000",
"matched_rule_name": "Example",
"tool_name": "Example",
"tool_input": {},
"status": "pending",
"created_at": "2026-01-15T12:00:00.000Z",
"expires_at": "2026-01-15T12:00:00.000Z",
"decided_at": "2026-01-15T12:00:00.000Z",
"decided_by": "string",
"decision_reason": "string",
"executed_at": "2026-01-15T12:00:00.000Z",
"execution_result": {}
}
]GET /v1/orgs/{orgId}/approvals/health
https://api.codespar.dev/v1/orgs/{orgId}/approvals/healthQueue depth and the age of the oldest pending approval
DEPRECATED alias of GET /v1/approvals/health (ent#979), kept for two releases. Same handler, same approvals:read scope.
The operator snapshot behind a queue alarm: how many approvals are pending for this org (for an API key or an OAuth token, for its own project) and how long the oldest has been waiting. status is a verdict on the sweeps only (degraded when last_sweep_at is null or older than 30 minutes, status_reason says which), never on queue depth, so alert on oldest_pending_age_seconds for that. Both counters are null or zero on an empty queue.
last_sweep_at is PLATFORM-WIDE, not per org: it is the last run of the expiry and orphan sweeps across the deployment. A stale value there means approvals may be sitting past their expires_at without being marked expired anywhere, which is a different fault from a deep queue.
An {orgId} that is not the credential's own org is 404.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
orgId | string | yes | — |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
404 | object | Not Found |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
last_sweep_at | string,null (date-time) | yes | Deployment-wide, not scoped to this org. |
oldest_pending_age_seconds | integer,null | yes | Null when nothing is pending. Rounded to whole seconds. |
pending_count | integer | yes | — |
status | "ok" | "degraded" | yes | — |
status_reason | "sweep_never_ran" | "sweep_stale" | yes | Why status is degraded; null exactly when it is ok. |
curl -X GET https://api.codespar.dev/v1/orgs/{orgId}/approvals/health \
-H "Authorization: Bearer $CODESPAR_API_KEY"GET /v1/orgs/{orgId}/approvals/health HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.get(
"https://api.codespar.dev/v1/orgs/{orgId}/approvals/health",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/orgs/{orgId}/approvals/health", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const result = await cs.api.get("/v1/orgs/{orgId}/approvals/health", {
path: {
orgId: "org_0000000000000000"
}
});{
"status": "ok",
"status_reason": "sweep_never_ran",
"pending_count": 1,
"oldest_pending_age_seconds": 0,
"last_sweep_at": "2026-01-15T12:00:00.000Z"
}GET /v1/orgs/{orgId}/approvals/{id}
https://api.codespar.dev/v1/orgs/{orgId}/approvals/{id}Read one approval, including the tool arguments held for review
DEPRECATED alias of GET /v1/approvals/{id} (ent#979), kept for two releases. Same handler, same approvals:read scope; switch the path and nothing else changes.
The whole row a rule parked for a human, tool_input included, which is what makes the decision reviewable rather than a yes or no on an id. That is the dashboard's view (service auth). An API key or an OAuth token reads only holds raised under its own project, and without tool_input; any other hold is the same 404 as one that does not exist. Two selected columns are deliberately off the wire: decision_history, and metadata, where the decision path records whether the approver's identity was verified or merely asserted.
An approval belonging to another org is indistinguishable from one that does not exist: both are 404, and an {orgId} that is not the credential's own org is 404 before any read.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string | yes | — |
orgId | string | yes | — |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
404 | object | Not Found |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
agent_id | string | yes | — |
created_at | string (date-time) | yes | — |
decided_at | string,null (date-time) | yes | — |
decided_by | string,null | yes | — |
decision_reason | string,null | yes | — |
executed_at | string,null (date-time) | yes | — |
execution_result | object,null | yes | — |
expires_at | string (date-time) | yes | — |
id | string | yes | — |
matched_rule_id | string | yes | — |
matched_rule_name | string | yes | — |
org_id | string | yes | — |
origin | object | object | yes | Who put the item in the inbox. A trigger's review is not an agent's approval: group by agent only the agent ones. |
project_id | string,null | yes | — |
session_id | string,null | yes | — |
status | "pending" | "approved" | "denied" | "expired" | "execution_failed" | yes | Closed by the table's own CHECK constraint, not by convention. |
tool_input | object | no | The tool arguments the rule held for review, verbatim. Absent unless the caller is the dashboard (service auth): an API key or an OAuth token never receives it. |
tool_name | string | yes | — |
curl -X GET https://api.codespar.dev/v1/orgs/{orgId}/approvals/{id} \
-H "Authorization: Bearer $CODESPAR_API_KEY"GET /v1/orgs/{orgId}/approvals/{id} HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.get(
"https://api.codespar.dev/v1/orgs/{orgId}/approvals/{id}",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/orgs/{orgId}/approvals/{id}", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const result = await cs.api.get("/v1/orgs/{orgId}/approvals/{id}", {
path: {
orgId: "org_0000000000000000",
id: "approval_0000000000000000"
}
});{
"id": "obj_0000000000000000",
"org_id": "org_0000000000000000",
"project_id": "prj_0000000000000000",
"session_id": "ses_0000000000000000",
"agent_id": "agt_0000000000000000",
"origin": {
"kind": "agent"
},
"matched_rule_id": "matchedrule_0000000000000000",
"matched_rule_name": "Example",
"tool_name": "Example",
"tool_input": {},
"status": "pending",
"created_at": "2026-01-15T12:00:00.000Z",
"expires_at": "2026-01-15T12:00:00.000Z",
"decided_at": "2026-01-15T12:00:00.000Z",
"decided_by": "string",
"decision_reason": "string",
"executed_at": "2026-01-15T12:00:00.000Z",
"execution_result": {}
}POST /v1/orgs/{orgId}/approvals/{id}/decide
https://api.codespar.dev/v1/orgs/{orgId}/approvals/{id}/decideApprove or deny a held call (deprecated path)
DEPRECATED alias of POST /v1/approvals/{id}/decide (ent#979), kept for two releases. Same handler, same scope; switch the path and nothing else changes.
reason is required in both directions, up to 8000 characters, and whitespace alone is refused. Deciding requires a PERSON, through the dashboard: only service auth may call it, and a project API key or an OAuth token gets bearer_token_cannot_decide before the body is read, with or without a user token. Already decided is 409 and expired is 410, both with the full approval in the body.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string | yes | — |
orgId | string | yes | — |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
decision | "approve" | "deny" | yes | — |
reason | string | yes | — |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | Body outside the schema. reason_invalid when the reason is the problem. Bare body. |
403 | object | The caller cannot decide: not the dashboard (a project API key or an OAuth token, with or without a user token), a user token that is missing/invalid/mismatched, or a person below admin. Bare body. |
404 | object | Does not exist, or belongs to another organization. Bare body. |
409 | object | Already decided; the full approval comes with it. Bare body. |
410 | object | Expired before anyone decided; the full approval comes with it. Bare body. |
429 | object | Attempt rate limit. retry_after_seconds says how long to wait. Bare body. |
503 | object | User token verification is not configured on this deployment. Bare body. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
agent_id | string | yes | — |
created_at | string (date-time) | yes | — |
decided_at | string,null (date-time) | yes | — |
decided_by | string,null | yes | — |
decision_reason | string,null | yes | — |
executed_at | string,null (date-time) | yes | — |
execution_result | object,null | yes | — |
expires_at | string (date-time) | yes | — |
id | string | yes | — |
matched_rule_id | string | yes | — |
matched_rule_name | string | yes | — |
org_id | string | yes | — |
origin | object | object | yes | Who put the item in the inbox. A trigger's review is not an agent's approval: group by agent only the agent ones. |
project_id | string,null | yes | — |
session_id | string,null | yes | — |
status | "pending" | "approved" | "denied" | "expired" | "execution_failed" | yes | Closed by the table's own CHECK constraint, not by convention. |
tool_input | object | no | The tool arguments the rule held for review, verbatim. Absent unless the caller is the dashboard (service auth): an API key or an OAuth token never receives it. |
tool_name | string | yes | — |
curl -X POST https://api.codespar.dev/v1/orgs/{orgId}/approvals/{id}/decide \
-H "Authorization: Bearer $CODESPAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"decision": "approve",
"reason": "string"
}'POST /v1/orgs/{orgId}/approvals/{id}/decide HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json
{
"decision": "approve",
"reason": "string"
}import os
import requests
res = requests.post(
"https://api.codespar.dev/v1/orgs/{orgId}/approvals/{id}/decide",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
json={
"decision": "approve",
"reason": "string"
},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/orgs/{orgId}/approvals/{id}/decide", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"decision": "approve",
"reason": "string"
}),
});
const data = await res.json();const r = await cs.api.response("post", "/v1/orgs/{orgId}/approvals/{id}/decide", {
path: {
orgId: "org_0000000000000000",
id: "approval_0000000000000000"
},
body: {
decision: "approve",
reason: "string"
}
});
// r.status is one of the documented statuses (200, 403),
// each with its own body shape in r.data; nothing here throws on 403.
if (r.ok) {
console.log(r.data);
}{
"id": "obj_0000000000000000",
"org_id": "org_0000000000000000",
"project_id": "prj_0000000000000000",
"session_id": "ses_0000000000000000",
"agent_id": "agt_0000000000000000",
"origin": {
"kind": "agent"
},
"matched_rule_id": "matchedrule_0000000000000000",
"matched_rule_name": "Example",
"tool_name": "Example",
"tool_input": {},
"status": "pending",
"created_at": "2026-01-15T12:00:00.000Z",
"expires_at": "2026-01-15T12:00:00.000Z",
"decided_at": "2026-01-15T12:00:00.000Z",
"decided_by": "string",
"decision_reason": "string",
"executed_at": "2026-01-15T12:00:00.000Z",
"execution_result": {}
}GET /v1/orgs/{orgId}/audit/config
https://api.codespar.dev/v1/orgs/{orgId}/audit/configRead the chain verification windows and staleness budgets
DEPRECATED alias of GET /v1/audit-events/config (ent#979), kept for two releases. Same handler, same audit:read scope.
The thresholds the verifier and GET /v1/orgs/{orgId}/audit/health measure against. An org with no stored row gets the defaults rather than a 404, with created_at and updated_at set to now, so a client cannot tell a seeded row from a defaulted one by the body alone.
An {orgId} that is not the credential's own org is 403 insufficient_role, which is the code this pair uses for a tenant mismatch rather than a role failure.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
orgId | string | yes | — |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
403 | object | The {orgId} in the path is not the authenticated org. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
cold_max_staleness_seconds | integer | yes | — |
created_at | string (date-time) | yes | — |
hot_max_staleness_seconds | integer | yes | — |
hot_window_days | integer | yes | — |
initial_walk_completed_at | string,null (date-time) | yes | Null until the first forward walk has reached the chain tip. |
org_id | string | yes | — |
updated_at | string (date-time) | yes | — |
verification_window_days | integer | yes | — |
warm_max_staleness_seconds | integer | yes | — |
warm_window_days | integer | yes | — |
curl -X GET https://api.codespar.dev/v1/orgs/{orgId}/audit/config \
-H "Authorization: Bearer $CODESPAR_API_KEY"GET /v1/orgs/{orgId}/audit/config HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.get(
"https://api.codespar.dev/v1/orgs/{orgId}/audit/config",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/orgs/{orgId}/audit/config", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const r = await cs.api.response("get", "/v1/orgs/{orgId}/audit/config", {
path: {
orgId: "org_0000000000000000"
}
});
// r.status is one of the documented statuses (200, 403),
// each with its own body shape in r.data; nothing here throws on 403.
if (r.ok) {
console.log(r.data);
}{
"org_id": "org_0000000000000000",
"verification_window_days": 0,
"hot_window_days": 0,
"hot_max_staleness_seconds": 1,
"warm_window_days": 0,
"warm_max_staleness_seconds": 1,
"cold_max_staleness_seconds": 1,
"initial_walk_completed_at": "2026-01-15T12:00:00.000Z",
"created_at": "2026-01-15T12:00:00.000Z",
"updated_at": "2026-01-15T12:00:00.000Z"
}PATCH /v1/orgs/{orgId}/audit/config
https://api.codespar.dev/v1/orgs/{orgId}/audit/configUpdate the chain verification windows and staleness budgets
DEPRECATED alias of PATCH /v1/audit-events/config (ent#979), kept for two releases. Same handler, same audit:write scope.
A sparse patch merged over the current row, or over the defaults when no row exists, and written back as an upsert; the full merged config is returned. A field whose value is not a JSON number is IGNORED rather than refused, so a typo in a key or a numeric string sent as text leaves the current value in place and still answers 200. A value that IS a number is taken as sent: the route does not check that it is a whole number, so send integers — the columns behind these fields are integers, and nothing in the request path will tell you a fraction was not what you meant.
Two invariants are checked on the MERGED result, not on the patch, so a one-field change can be refused because of a field you did not send: hot_max_staleness_seconds may not exceed 259200 seconds (72 hours), the regulatory floor on how stale verification may get, and the windows must stay ordered hot within warm within the verification window.
AUTHORIZATION IS SERVICE AUTH ONLY, exactly as on the acknowledge route: a bearer key or an OAuth access token is 403 human_session_required before the body is read, a missing x-codespar-user is 401, and the named user must hold admin or owner.
An {orgId} that is not the credential's own org is 403 insufficient_role.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
orgId | string | yes | — |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
cold_max_staleness_seconds | number | no | — |
hot_max_staleness_seconds | number | no | — |
hot_window_days | number | no | — |
verification_window_days | number | no | — |
warm_max_staleness_seconds | number | no | — |
warm_window_days | number | no | — |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | object | The merged config broke an invariant. limit carries the ceiling on regulatory_minimum_violation, and the three merged window values on window_ordering_invalid. |
401 | object | No x-codespar-user header on a service credential. |
403 | object | The {orgId} is not the authenticated org, a project API key or an OAuth access token was used, or the named user is below admin. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
cold_max_staleness_seconds | integer | yes | — |
created_at | string (date-time) | yes | — |
hot_max_staleness_seconds | integer | yes | — |
hot_window_days | integer | yes | — |
initial_walk_completed_at | string,null (date-time) | yes | Null until the first forward walk has reached the chain tip. |
org_id | string | yes | — |
updated_at | string (date-time) | yes | — |
verification_window_days | integer | yes | — |
warm_max_staleness_seconds | integer | yes | — |
warm_window_days | integer | yes | — |
curl -X PATCH https://api.codespar.dev/v1/orgs/{orgId}/audit/config \
-H "Authorization: Bearer $CODESPAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"verification_window_days": 0,
"hot_window_days": 0,
"hot_max_staleness_seconds": 1,
"warm_window_days": 0,
"warm_max_staleness_seconds": 1,
"cold_max_staleness_seconds": 1
}'PATCH /v1/orgs/{orgId}/audit/config HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json
{
"verification_window_days": 0,
"hot_window_days": 0,
"hot_max_staleness_seconds": 1,
"warm_window_days": 0,
"warm_max_staleness_seconds": 1,
"cold_max_staleness_seconds": 1
}import os
import requests
res = requests.patch(
"https://api.codespar.dev/v1/orgs/{orgId}/audit/config",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
json={
"verification_window_days": 0,
"hot_window_days": 0,
"hot_max_staleness_seconds": 1,
"warm_window_days": 0,
"warm_max_staleness_seconds": 1,
"cold_max_staleness_seconds": 1
},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/orgs/{orgId}/audit/config", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"verification_window_days": 0,
"hot_window_days": 0,
"hot_max_staleness_seconds": 1,
"warm_window_days": 0,
"warm_max_staleness_seconds": 1,
"cold_max_staleness_seconds": 1
}),
});
const data = await res.json();const r = await cs.api.response("patch", "/v1/orgs/{orgId}/audit/config", {
path: {
orgId: "org_0000000000000000"
},
body: {
verification_window_days: 0,
hot_window_days: 0,
hot_max_staleness_seconds: 1,
warm_window_days: 0,
warm_max_staleness_seconds: 1,
cold_max_staleness_seconds: 1
}
});
// r.status is one of the documented statuses (200, 403),
// each with its own body shape in r.data; nothing here throws on 403.
if (r.ok) {
console.log(r.data);
}{
"org_id": "org_0000000000000000",
"verification_window_days": 0,
"hot_window_days": 0,
"hot_max_staleness_seconds": 1,
"warm_window_days": 0,
"warm_max_staleness_seconds": 1,
"cold_max_staleness_seconds": 1,
"initial_walk_completed_at": "2026-01-15T12:00:00.000Z",
"created_at": "2026-01-15T12:00:00.000Z",
"updated_at": "2026-01-15T12:00:00.000Z"
}GET /v1/orgs/{orgId}/audit/export
https://api.codespar.dev/v1/orgs/{orgId}/audit/exportExport audit events, with a signed manifest (deprecated path)
DEPRECATED alias of GET /v1/audit-events/export (ent#979), kept for two releases. Same handler, same scope; an {orgId} that is not the credential's own is refused.
The response is STREAMED row by row, with the signed manifest at the end: the manifest covers the scope (including the narrowings applied), not just the content. Full detail on the canonical path.
An API key or an OAuth token is refused with 403 audit_export_key_unavailable: the export covers every project of the organization and answers service auth only until it can be narrowed to one project.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
orgId | string | yes | — |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
agent_id | string | no | — |
from | string | yes | Start of the window, ISO 8601. Required. |
to | string | yes | End of the window, ISO 8601. Required. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | Query outside the schema, a date that is not ISO 8601, or from after to. Bare body. |
403 | object | forbidden: the credential cannot export this organization's audit events. audit_export_key_unavailable: the caller is an API key or an OAuth token. Bare body. |
413 | object | The window goes past the row ceiling; narrow it. Bare body. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
export_id | string | yes | — |
export_signature | object | yes | — |
key_id | string | yes | — |
manifest | object | yes | — |
org_id | string | yes | — |
rows | array of object | yes | — |
curl -X GET https://api.codespar.dev/v1/orgs/{orgId}/audit/export \
-H "Authorization: Bearer $CODESPAR_API_KEY"GET /v1/orgs/{orgId}/audit/export HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.get(
"https://api.codespar.dev/v1/orgs/{orgId}/audit/export",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/orgs/{orgId}/audit/export", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const r = await cs.api.response("get", "/v1/orgs/{orgId}/audit/export", {
path: {
orgId: "org_0000000000000000"
},
query: {
from: "string",
to: "string"
}
});
// r.status is one of the documented statuses (200, 403),
// each with its own body shape in r.data; nothing here throws on 403.
if (r.ok) {
console.log(r.data);
}{
"export_id": "export_0000000000000000",
"org_id": "org_0000000000000000",
"rows": [
{}
],
"manifest": {},
"export_signature": {},
"key_id": "key_0000000000000000"
}GET /v1/orgs/{orgId}/audit/health
https://api.codespar.dev/v1/orgs/{orgId}/audit/healthHow far the chain verifier got, and whether what it walked was linked
DEPRECATED alias of GET /v1/audit-events/health (ent#979), kept for two releases. Same handler, same audit:read scope.
Read actionable_status first; the four legacy top-level fields (status, last_sequence_number, last_checked_at, detail) are kept unchanged for callers written before the expanded body and answer a narrower question.
The two fields that carry bad news quietly are inside verification.chain_link_check. unverifiable_segments above zero means a stretch of the chain has no verified link, so nothing certifies it either way, and watermark_pinned_by_break_at non-null means nothing ABOVE that sequence is verified at all: the forward walk stops at the break. Neither clears by acknowledging an incident.
An {orgId} that is not the credential's own org is 403 forbidden.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
orgId | string | yes | — |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
403 | object | The {orgId} in the path is not the authenticated org. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
actionable_status | "verifying" | "healthy" | "catching_up" | "link_unverifiable" | "degraded" | "broken" | yes | The field to alert on. link_unverifiable is not degraded: it means part of the chain was never verified either way, whereas degraded means tampering was detected. |
detail | string | yes | — |
incidents | object | yes | — |
initial_walk | object | yes | — |
last_checked_at | string (date-time) | yes | — |
last_sequence_number | integer | yes | — |
status | "healthy" | "degraded" | yes | — |
verification | object | yes | — |
curl -X GET https://api.codespar.dev/v1/orgs/{orgId}/audit/health \
-H "Authorization: Bearer $CODESPAR_API_KEY"GET /v1/orgs/{orgId}/audit/health HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.get(
"https://api.codespar.dev/v1/orgs/{orgId}/audit/health",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/orgs/{orgId}/audit/health", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const r = await cs.api.response("get", "/v1/orgs/{orgId}/audit/health", {
path: {
orgId: "org_0000000000000000"
}
});
// r.status is one of the documented statuses (200, 403),
// each with its own body shape in r.data; nothing here throws on 403.
if (r.ok) {
console.log(r.data);
}{
"status": "healthy",
"last_sequence_number": 0,
"last_checked_at": "2026-01-15T12:00:00.000Z",
"detail": "string",
"actionable_status": "verifying",
"verification": {
"window_days": 0,
"watermark_sequence": 0,
"watermark_at": "2026-01-15T12:00:00.000Z",
"watermark_entry_hash": "string",
"coverage_ratio": 0,
"hot_bucket_oldest_verified_at": "2026-01-15T12:00:00.000Z",
"hot_bucket_max_staleness_seconds": 1,
"writer_tip_check_passed": true,
"writer_tip_check_at": "2026-01-15T12:00:00.000Z",
"chain_link_check": {
"unverifiable_segments": 0,
"oldest_unverifiable_segment": {
"from_sequence": 0,
"to_sequence": 0,
"reason": "sequence_gap",
"first_detected_at": "2026-01-15T12:00:00.000Z"
},
"writer_tip_link_unverifiable": true,
"watermark_pinned_by_break_at": 0
}
},
"incidents": {
"open_count": 1,
"acknowledged_count_30d": 1,
"open_truncated": true,
"open": [
{
"id": "obj_0000000000000000",
"detected_at": "2026-01-15T12:00:00.000Z",
"detected_at_sequence": 0
}
]
},
"initial_walk": {
"in_progress": true,
"started_at": "2026-01-15T12:00:00.000Z",
"events_processed": 0
}
}GET /v1/orgs/{orgId}/audit/incidents
https://api.codespar.dev/v1/orgs/{orgId}/audit/incidentsList chain-integrity incidents
DEPRECATED alias of GET /v1/audit-events/incidents (ent#979), kept for two releases. Same handler, same audit:read scope.
Newest first by detected_at, capped at 100 rows and NOT paginated: there is no cursor, so an org with more than 100 incidents cannot be walked past the first page from this route.
An {orgId} that is not the credential's own org is 403 forbidden.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
orgId | string | yes | — |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
status | string | no | Comma-separated, from open, acknowledged and all; all wins over anything sent with it. Default open. Unknown tokens are DROPPED rather than refused, and the request is only 400 invalid_status when nothing recognisable survives. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | No recognisable value survived the status filter. |
403 | object | The {orgId} in the path is not the authenticated org. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
incidents | array of object | yes | — |
curl -X GET https://api.codespar.dev/v1/orgs/{orgId}/audit/incidents \
-H "Authorization: Bearer $CODESPAR_API_KEY"GET /v1/orgs/{orgId}/audit/incidents HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.get(
"https://api.codespar.dev/v1/orgs/{orgId}/audit/incidents",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/orgs/{orgId}/audit/incidents", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const r = await cs.api.response("get", "/v1/orgs/{orgId}/audit/incidents", {
path: {
orgId: "org_0000000000000000"
}
});
// r.status is one of the documented statuses (200, 403),
// each with its own body shape in r.data; nothing here throws on 403.
if (r.ok) {
console.log(r.data);
}{
"incidents": [
{
"id": "obj_0000000000000000",
"org_id": "org_0000000000000000",
"detected_at": "2026-01-15T12:00:00.000Z",
"detected_at_sequence": 0,
"discovered_by_run_id": "discoveredbyrun_0000000000000000",
"status": "open",
"acknowledged_at": "2026-01-15T12:00:00.000Z",
"acknowledged_by": "string",
"incident_reference": "string",
"resolution_summary": "string",
"anpd_notification_status": "not_required",
"personal_data_affected": true,
"affected_from_sequence": 0,
"affected_to_sequence": 0,
"ack_event_entry_hash": "string",
"created_at": "2026-01-15T12:00:00.000Z"
}
]
}POST /v1/orgs/{orgId}/audit/incidents/{id}/acknowledge
https://api.codespar.dev/v1/orgs/{orgId}/audit/incidents/{id}/acknowledgeAcknowledge a chain-integrity incident and write the acknowledgement into the chain
DEPRECATED alias of POST /v1/audit-events/incidents/{id}/acknowledge (ent#979), kept for two releases. Same handler, same audit:write scope.
Not a status flip: the acknowledgement is itself appended to the audit chain, and the incident row records the entry it wrote in ack_event_entry_hash. The append and the open to acknowledged transition share one transaction, so a reader never sees one without the other. chain_sequence_number in the response is that entry's sequence.
AUTHORIZATION IS SERVICE AUTH ONLY. A project API key (bearer) or an OAuth access token is refused with 403 human_session_required before the body is read, whatever x-codespar-user says; the call has to arrive on a service credential carrying x-codespar-user, and that user must hold admin or owner in the org. A missing header is 401 unauthenticated.
A second acknowledgement, including one that loses a concurrent race, is 409 already_acknowledged with the current row attached rather than an opaque conflict. 503 lock_timeout means the chain lock was not available and nothing was written; that one is safe to retry.
An {orgId} that is not the credential's own org is 404 incident_not_found, the same answer as an id that does not exist.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string | yes | — |
orgId | string | yes | — |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
affected_from_sequence | number,null | no | Send with affected_to_sequence or with neither; one without the other is 400 paired_required. The route checks only that the value is a JSON number and that from is not greater than to. It does NOT check integrality, so this schema does not claim an integer the route would accept a fraction for; send whole numbers, because the column behind it is an integer. |
affected_to_sequence | number,null | no | Paired with affected_from_sequence; same rules. |
anpd_notification_status | "not_required" | "notified" | yes | Required and terminal. pending_assessment is refused with 400 pending_assessment_not_terminal: it is a console state and is never persisted. |
incident_reference | string,null | no | Optional. Control-stripped first; the 256 character ceiling is applied to the stripped value. Unlike resolution_summary this one is not trimmed and an empty string is accepted. |
personal_data_affected | boolean | yes | — |
resolution_summary | string | yes | Required, and whitespace alone does not satisfy it. The route strips C0 control characters and DEL, then refuses a value whose TRIMMED remainder is empty with 400 field_invalid / empty — so " " is rejected, not accepted. The 8000 character ceiling is then applied to the stripped value BEFORE trimming, so surrounding whitespace does count against the limit even though it cannot satisfy the emptiness check. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | object | A terminal ANPD status is required, or one field failed its check. field names it and reason is one of empty, too_long, invalid_type, invalid_enum, paired_required or range_inconsistent; limit is present only on too_long. resolution_summary reports empty both when it is missing or not a string and when it is present but blank after trimming. |
401 | object | No x-codespar-user header on a service credential. |
403 | object | A project API key or an OAuth access token cannot acknowledge (human_session_required), or the named user is below admin. |
404 | object | No such incident for this org. |
409 | object | Already acknowledged. The current row is attached, so a retry needs no second read. |
503 | object | The per-org chain lock was not available. Nothing was written; retry. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
chain_sequence_number | integer | yes | Sequence of the chain entry this acknowledgement appended. |
incident | object | yes | — |
curl -X POST https://api.codespar.dev/v1/orgs/{orgId}/audit/incidents/{id}/acknowledge \
-H "Authorization: Bearer $CODESPAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"resolution_summary": "string",
"anpd_notification_status": "not_required",
"personal_data_affected": true,
"incident_reference": "string",
"affected_from_sequence": 0,
"affected_to_sequence": 0
}'POST /v1/orgs/{orgId}/audit/incidents/{id}/acknowledge HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json
{
"resolution_summary": "string",
"anpd_notification_status": "not_required",
"personal_data_affected": true,
"incident_reference": "string",
"affected_from_sequence": 0,
"affected_to_sequence": 0
}import os
import requests
res = requests.post(
"https://api.codespar.dev/v1/orgs/{orgId}/audit/incidents/{id}/acknowledge",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
json={
"resolution_summary": "string",
"anpd_notification_status": "not_required",
"personal_data_affected": True,
"incident_reference": "string",
"affected_from_sequence": 0,
"affected_to_sequence": 0
},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/orgs/{orgId}/audit/incidents/{id}/acknowledge", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"resolution_summary": "string",
"anpd_notification_status": "not_required",
"personal_data_affected": true,
"incident_reference": "string",
"affected_from_sequence": 0,
"affected_to_sequence": 0
}),
});
const data = await res.json();const r = await cs.api.response("post", "/v1/orgs/{orgId}/audit/incidents/{id}/acknowledge", {
path: {
orgId: "org_0000000000000000",
id: "incident_0000000000000000"
},
body: {
resolution_summary: "string",
anpd_notification_status: "not_required",
personal_data_affected: true,
incident_reference: "string",
affected_from_sequence: 0,
affected_to_sequence: 0
}
});
// r.status is one of the documented statuses (200, 403),
// each with its own body shape in r.data; nothing here throws on 403.
if (r.ok) {
console.log(r.data);
}{
"incident": {
"id": "obj_0000000000000000",
"org_id": "org_0000000000000000",
"detected_at": "2026-01-15T12:00:00.000Z",
"detected_at_sequence": 0,
"discovered_by_run_id": "discoveredbyrun_0000000000000000",
"status": "open",
"acknowledged_at": "2026-01-15T12:00:00.000Z",
"acknowledged_by": "string",
"incident_reference": "string",
"resolution_summary": "string",
"anpd_notification_status": "not_required",
"personal_data_affected": true,
"affected_from_sequence": 0,
"affected_to_sequence": 0,
"ack_event_entry_hash": "string",
"created_at": "2026-01-15T12:00:00.000Z"
},
"chain_sequence_number": 0
}POST /v1/orgs/{orgId}/data-subjects/{userId}/anonymize
https://api.codespar.dev/v1/orgs/{orgId}/data-subjects/{userId}/anonymizeErase a data subject's personal data across the audit chain (LGPD Art. 18)
Takes no body: the subject is the {userId} path segment, matched against user_id in the chain's event payloads. Requires the data-subjects:anonymize scope.
The call writes a dsr_request chain event, then splits every matching row in two. Rows under a fiscal hold (settlement and invoice events, plus anything whose payload sets fiscal_hold) are NOT erased now: they are scheduled for five years after the event's own timestamp and reported in deferred_entries. Everything else has its personal fields nulled in place, in one transaction with a single witness event that covers the whole batch.
IDEMPOTENT BY CONSTRUCTION, but the counters are not. Re-running for the same subject finds the already-tombstoned rows excluded and the deferred rows already scheduled, so it erases nothing twice; it still appends a new dsr_request event and answers with zeroes. Do not read a second call's zeroes as 'nothing matched'.
A {userId} that is empty or longer than 256 characters is 400. A {orgId} that is not the credential's own org is 403 forbidden.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
orgId | string | yes | — |
userId | string | yes | 1 to 256 characters. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | {userId} was empty or over 256 characters. |
403 | object | The {orgId} in the path is not the authenticated org. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
deferred_entries | array of object | yes | — |
dsr_event_entry_hash | string | yes | — |
dsr_id | string | yes | The entry hash of the dsr_request chain event this call wrote. |
rows_anonymized_immediately | integer | yes | — |
rows_deferred | integer | yes | — |
curl -X POST https://api.codespar.dev/v1/orgs/{orgId}/data-subjects/{userId}/anonymize \
-H "Authorization: Bearer $CODESPAR_API_KEY"POST /v1/orgs/{orgId}/data-subjects/{userId}/anonymize HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.post(
"https://api.codespar.dev/v1/orgs/{orgId}/data-subjects/{userId}/anonymize",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/orgs/{orgId}/data-subjects/{userId}/anonymize", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const r = await cs.api.response("post", "/v1/orgs/{orgId}/data-subjects/{userId}/anonymize", {
path: {
orgId: "org_0000000000000000",
userId: "user_0000000000000000"
}
});
// r.status is one of the documented statuses (200, 403),
// each with its own body shape in r.data; nothing here throws on 403.
if (r.ok) {
console.log(r.data);
}{
"dsr_id": "dsr_0000000000000000",
"dsr_event_entry_hash": "string",
"rows_anonymized_immediately": 0,
"rows_deferred": 0,
"deferred_entries": [
{
"event_id": "event_0000000000000000",
"anonymize_after": "2026-01-15T12:00:00.000Z"
}
]
}GET /v1/orgs/{orgId}/mandates
https://api.codespar.dev/v1/orgs/{orgId}/mandatesList the org's mandates
Newest first, capped by limit (max 100) and NOT paginated: there is no cursor, so an org with more mandates than limit cannot be walked past the first page from this route. Requires mandates:read.
status DEFAULTS TO active, so an omitted filter hides revoked and expired mandates rather than returning everything; count is the length of the returned page and not a total.
THE {orgId} SEGMENT IS NOT READ. The list is always the credential's own org, so a different value in the path changes nothing about the answer. Do not use the path as a tenant filter.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
orgId | string | yes | — |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
agent_id | string | no | — |
created_after | integer,null | no | — |
limit | integer | no | — |
status | "active" | "revoked" | "expired" | no | — |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | The query failed the schema; details.issues carries the Zod issues. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
count | integer | yes | Length of mandates, not a total. |
mandates | array of object | yes | — |
curl -X GET https://api.codespar.dev/v1/orgs/{orgId}/mandates \
-H "Authorization: Bearer $CODESPAR_API_KEY"GET /v1/orgs/{orgId}/mandates HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.get(
"https://api.codespar.dev/v1/orgs/{orgId}/mandates",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/orgs/{orgId}/mandates", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const result = await cs.api.get("/v1/orgs/{orgId}/mandates", {
path: {
orgId: "org_0000000000000000"
}
});{
"mandates": [
{
"id": "obj_0000000000000000",
"org_id": "org_0000000000000000",
"agent_id": "agt_0000000000000000",
"type": "payment",
"status": "active",
"format_version": 0,
"secret_version": 0,
"amount": "1000",
"remaining_amount": "1000",
"currency": "BRL",
"purposes": [
"string"
],
"max_amount": "1000",
"denomination": "string",
"parent_id": "parent_0000000000000000",
"expires_at": "2026-01-15T12:00:00.000Z",
"revoked_at": "2026-01-15T12:00:00.000Z",
"revocation_reason": "string",
"expired_at": "2026-01-15T12:00:00.000Z",
"signature": "string",
"authorized_by": "string",
"created_at": "2026-01-15T12:00:00.000Z"
}
],
"count": 1
}POST /v1/orgs/{orgId}/mandates
https://api.codespar.dev/v1/orgs/{orgId}/mandatesIssue an org-side mandate (off by default, see the 501)
OFF BY DEFAULT, AND THE FIRST THING THIS ROUTE DOES IS CHECK THAT. Unless the deployment has explicitly enabled org-side issuance, every call is 501 not_implemented before any row is written. On a default deployment that 501 is the expected answer, not an outage.
The 501 is about offering issuance, not about enforcement: POST /v1/wallets/{id}/execute charges a mandate before the payment is attempted, decrements remaining_amount under a guard that refuses an over-cap spend, and writes the consumption row this document returns from GET /v1/orgs/{orgId}/mandates/{id}. So a mandate issued here is enforceable; whether it can be issued at all is the deployment's flag.
When enabled, issuance is gated by the org's policy rules for codespar:mandate:issue, evaluated before any write. The three non-allowed outcomes are distinct on purpose: 403 policy_denied is a rule refusing, 413 payload_too_large is an input too big to evaluate, and 503 policy_engine_error means the engine could NOT evaluate. The 503 is infrastructure and is retriable; the 403 is not.
expires_at is UNIX SECONDS and must be in the future. purposes must be printable ASCII, and the mandate is spendable only for a purpose in that list. amount and max_amount are decimal strings with at most two places.
The 201 body has two shapes and the request does not choose between them: a REGISTERED agent (active, with an active key) gets a dual-signed presentation token plus format_version, agent_did and kid, and an unregistered one gets the plain token and nothing else. principal_kyc_verified is always false.
THE {orgId} SEGMENT IS NOT READ. The mandate is always issued for the credential's own org. A body over 1 MiB is refused by the server before the handler runs, with the framework's own error body rather than the envelope below.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
orgId | string | yes | — |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
agent_id | string | yes | — |
amount | string | yes | — |
currency | string | yes | — |
denomination | string,null | no | — |
expires_at | integer | yes | — |
max_amount | string,null | no | — |
parent_id | string,null | no | — |
purposes | array of string | yes | — |
type | "payment" | "subscription" | "delegation" | yes | — |
Responses
| Status | Body | Description |
|---|---|---|
201 | object | object | OK |
400 | object | The body failed the schema (details.issues carries the Zod issues), expires_at was not in the future, or a purpose was not printable ASCII. |
403 | object | A policy rule refused issuance. details carries reason, ruleType and ruleId, plus approval_id and expires_at when the rule opened an approval instead of denying outright. |
413 | object | The policy input was too large to evaluate. |
501 | object | Org-side issuance is disabled on this deployment. No mandate was created. |
503 | object | The policy engine could not evaluate the request. Nothing was evaluated and nothing was issued; retry. |
curl -X POST https://api.codespar.dev/v1/orgs/{orgId}/mandates \
-H "Authorization: Bearer $CODESPAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "agt_0000000000000000",
"type": "payment",
"amount": "1000",
"currency": "BRL",
"purposes": [
"string"
],
"expires_at": 0,
"max_amount": "1000",
"denomination": "string",
"parent_id": "parent_0000000000000000"
}'POST /v1/orgs/{orgId}/mandates HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json
{
"agent_id": "agt_0000000000000000",
"type": "payment",
"amount": "1000",
"currency": "BRL",
"purposes": [
"string"
],
"expires_at": 0,
"max_amount": "1000",
"denomination": "string",
"parent_id": "parent_0000000000000000"
}import os
import requests
res = requests.post(
"https://api.codespar.dev/v1/orgs/{orgId}/mandates",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
json={
"agent_id": "agt_0000000000000000",
"type": "payment",
"amount": "1000",
"currency": "BRL",
"purposes": [
"string"
],
"expires_at": 0,
"max_amount": "1000",
"denomination": "string",
"parent_id": "parent_0000000000000000"
},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/orgs/{orgId}/mandates", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"agent_id": "agt_0000000000000000",
"type": "payment",
"amount": "1000",
"currency": "BRL",
"purposes": [
"string"
],
"expires_at": 0,
"max_amount": "1000",
"denomination": "string",
"parent_id": "parent_0000000000000000"
}),
});
const data = await res.json();const r = await cs.api.response("post", "/v1/orgs/{orgId}/mandates", {
path: {
orgId: "org_0000000000000000"
},
body: {
agent_id: "agt_0000000000000000",
type: "payment",
amount: "1000",
currency: "BRL",
purposes: [
"string"
],
expires_at: 0,
max_amount: "1000",
denomination: "string",
parent_id: "parent_0000000000000000"
}
});
// r.status is one of the documented statuses (200, 403),
// each with its own body shape in r.data; nothing here throws on 403.
if (r.ok) {
console.log(r.data);
}{
"mandate_id": "mandate_0000000000000000",
"signed_token": "string",
"format_version": 3,
"agent_did": "did:web:codespar.dev:org:agent",
"kid": "string",
"principal_kyc_verified": false,
"expires_at": "2026-01-15T12:00:00.000Z"
}GET /v1/orgs/{orgId}/mandates/{id}
https://api.codespar.dev/v1/orgs/{orgId}/mandates/{id}Read one mandate with its consumption history
The mandate row plus every recorded consumption, oldest first. Requires mandates:read.
BOTH HALVES MOVE. remaining_amount is the cap as stored and consumptions is the charge history, and the spend path that changes them is POST /v1/wallets/{id}/execute: it charges this mandate before the payment is attempted, decrementing the cap and appending the consumption row in one transaction, and refuses the spend when the cap would be exceeded. A wallet execute that never got past that gate leaves nothing here.
A consumption's tool_call_id is the execute's attempt_id and is unique per org, so a retried execute reusing its attempt_id is charged once: a retry adds no second row and subtracts nothing twice. Reading one row per payment attempt would therefore undercount attempts, not charges.
A mandate belonging to another org is indistinguishable from one that does not exist: both are 404. THE {orgId} SEGMENT IS NOT READ, so the isolation comes from the query, not from the path.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string | yes | — |
orgId | string | yes | — |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
404 | object | No such mandate for this org. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
agent_id | string | yes | — |
amount | string | yes | Decimal string. |
authorized_by | string | yes | — |
consumptions | array of object | yes | — |
created_at | string (date-time) | yes | — |
currency | string | yes | — |
denomination | string,null | yes | — |
expired_at | string,null (date-time) | yes | — |
expires_at | string (date-time) | yes | — |
format_version | integer | yes | — |
id | string | yes | mnd_ prefixed. |
max_amount | string,null | yes | — |
org_id | string | yes | — |
parent_id | string,null | yes | — |
purposes | array of string | yes | — |
remaining_amount | string | yes | Decimal string, and it MOVES. This is the aggregate cap still available on the mandate: POST /v1/wallets/{id}/execute charges this mandate before the payment is attempted, decrementing this value and recording the consumption in one transaction. A spend that would take it below zero is refused with mandate_cap_exceeded rather than clamped, so this never goes negative. |
revocation_reason | string,null | yes | — |
revoked_at | string,null (date-time) | yes | — |
secret_version | integer | yes | — |
signature | string | yes | — |
status | "active" | "revoked" | "expired" | yes | Closed by the table's own CHECK constraint. |
type | "payment" | "subscription" | "delegation" | yes | — |
curl -X GET https://api.codespar.dev/v1/orgs/{orgId}/mandates/{id} \
-H "Authorization: Bearer $CODESPAR_API_KEY"GET /v1/orgs/{orgId}/mandates/{id} HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.get(
"https://api.codespar.dev/v1/orgs/{orgId}/mandates/{id}",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/orgs/{orgId}/mandates/{id}", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const result = await cs.api.get("/v1/orgs/{orgId}/mandates/{id}", {
path: {
orgId: "org_0000000000000000",
id: "mandate_0000000000000000"
}
});{
"id": "obj_0000000000000000",
"org_id": "org_0000000000000000",
"agent_id": "agt_0000000000000000",
"type": "payment",
"status": "active",
"format_version": 0,
"secret_version": 0,
"amount": "1000",
"remaining_amount": "1000",
"currency": "BRL",
"purposes": [
"string"
],
"max_amount": "1000",
"denomination": "string",
"parent_id": "parent_0000000000000000",
"expires_at": "2026-01-15T12:00:00.000Z",
"revoked_at": "2026-01-15T12:00:00.000Z",
"revocation_reason": "string",
"expired_at": "2026-01-15T12:00:00.000Z",
"signature": "string",
"authorized_by": "string",
"created_at": "2026-01-15T12:00:00.000Z",
"consumptions": [
{
"id": "obj_0000000000000000",
"mandate_id": "mandate_0000000000000000",
"amount": "1000",
"call_purpose": "string",
"tool_call_id": "tc_0000000000000000",
"consumed_at": "2026-01-15T12:00:00.000Z"
}
]
}POST /v1/orgs/{orgId}/mandates/{id}/revoke
https://api.codespar.dev/v1/orgs/{orgId}/mandates/{id}/revokeRevoke a mandate
Requires mandates:write. The body is optional and carries at most a reason; anything else is ignored, and a missing or blank reason is recorded as revoked by operator.
NOT IDEMPOTENT, deliberately. Revoking an already-revoked mandate is 409 and an already-expired one is 410, each naming which state it was in, rather than repeating the first call's answer. prior_status on the 200 is therefore always active.
The revocation notifies subscribers inside the same transaction as the status change, so a subscriber never learns of a revocation that then rolls back. THE {orgId} SEGMENT IS NOT READ; the mandate is resolved against the credential's own org.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
id | string | yes | — |
orgId | string | yes | — |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | no | Free text, trimmed. An absent, non-string or blank value is recorded as revoked by operator. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
404 | object | No such mandate for this org. |
409 | object | The mandate was already revoked. |
410 | object | The mandate had already expired. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
prior_status | "active" | yes | — |
revoked_at | string (date-time) | yes | — |
curl -X POST https://api.codespar.dev/v1/orgs/{orgId}/mandates/{id}/revoke \
-H "Authorization: Bearer $CODESPAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "string"
}'POST /v1/orgs/{orgId}/mandates/{id}/revoke HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json
{
"reason": "string"
}import os
import requests
res = requests.post(
"https://api.codespar.dev/v1/orgs/{orgId}/mandates/{id}/revoke",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
json={
"reason": "string"
},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/orgs/{orgId}/mandates/{id}/revoke", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"reason": "string"
}),
});
const data = await res.json();const result = await cs.api.post("/v1/orgs/{orgId}/mandates/{id}/revoke", {
path: {
orgId: "org_0000000000000000",
id: "mandate_0000000000000000"
},
body: {
reason: "string"
}
});{
"revoked_at": "2026-01-15T12:00:00.000Z",
"prior_status": "active"
}GET /v1/orgs/{orgId}/pause
https://api.codespar.dev/v1/orgs/{orgId}/pauseRead the organization kill switch
Whether the organization has paused ALL agent spend. While paused is true every agent money door of the organization refuses with org_paused (403): consumer payments and spends under a mandate, payment links, consumer withdrawals and transfers, card authorizations, the money-moving meta-tool actions, the org programmable wallet, an inbound Pix devolution, and raw tools the deny-list classifies as a fund transfer. Reads, inbound charges, mandate revoke/pause and x402-in keep running. GET /v1/mandates/{id} carries the same flag as org_paused.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
orgId | string | yes | — |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
404 | object | The {orgId} in the path is not the authenticated org. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
generation | integer | yes | Increases by one on every pause and every resume. |
org_id | string | yes | — |
paused | boolean | yes | — |
paused_at | string,null (date-time) | yes | — |
paused_by | string,null | yes | The admin's user id, or key:<id> for a bearer key. Null when not paused. |
paused_by_source | "bearer_key" | "user_token_verified" | "header_asserted" | yes | — |
reason | string,null | yes | Why it was paused; null when not paused. |
resumed_at | string,null (date-time) | yes | The last resume, kept after a new pause. |
curl -X GET https://api.codespar.dev/v1/orgs/{orgId}/pause \
-H "Authorization: Bearer $CODESPAR_API_KEY"GET /v1/orgs/{orgId}/pause HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEYimport os
import requests
res = requests.get(
"https://api.codespar.dev/v1/orgs/{orgId}/pause",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/orgs/{orgId}/pause", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
},
});
const data = await res.json();const result = await cs.api.get("/v1/orgs/{orgId}/pause", {
path: {
orgId: "org_0000000000000000"
}
});{
"org_id": "org_0000000000000000",
"paused": true,
"paused_at": "2026-01-15T12:00:00.000Z",
"reason": "string",
"paused_by": "string",
"paused_by_source": "bearer_key",
"resumed_at": "2026-01-15T12:00:00.000Z",
"generation": 0
}POST /v1/orgs/{orgId}/pause
https://api.codespar.dev/v1/orgs/{orgId}/pausePause all agent spend of the organization (kill switch)
Stops every agent money door of the organization, test and live projects alike, until a person resumes it. Once this call answers, no new reservation is taken: a spend that already reserved its funds finishes, like an in-flight hold on a frozen wallet.
A bearer key needs the organizations:pause scope; the dashboard path needs an admin. Idempotent: pausing a paused organization answers changed: false and keeps the original pause. Resuming is NOT an API-key operation: it is done from the dashboard by a verified admin. Publishes commerce.organization.paused to every project of the organization and appends org_spend_paused to its audit chain.
Card authorizations are refused at the issuer that waits for our answer (Pomelo). Bridge decides before we hear of it: freeze those cards at the issuer as well.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
orgId | string | yes | — |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | no | — |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | OK |
400 | object | The body did not parse. |
403 | object | The dashboard path named no admin, or a token that did not verify; or the credential is an OAuth token, which cannot pause (bearer_token_cannot_decide: only a bearer key, as itself, or the dashboard, as a person, presses the switch). Bare { error, remediation } body. |
404 | object | The {orgId} in the path is not the authenticated org. |
Response 200
| Field | Type | Required | Description |
|---|---|---|---|
changed | boolean | yes | False when the organization was already in the requested state. |
pause | object | yes | — |
curl -X POST https://api.codespar.dev/v1/orgs/{orgId}/pause \
-H "Authorization: Bearer $CODESPAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "string"
}'POST /v1/orgs/{orgId}/pause HTTP/1.1
Host: api.codespar.dev
Authorization: Bearer $CODESPAR_API_KEY
Content-Type: application/json
{
"reason": "string"
}import os
import requests
res = requests.post(
"https://api.codespar.dev/v1/orgs/{orgId}/pause",
headers={"Authorization": f"Bearer {os.environ['CODESPAR_API_KEY']}"},
json={
"reason": "string"
},
)
res.raise_for_status()
data = res.json()const res = await fetch("https://api.codespar.dev/v1/orgs/{orgId}/pause", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CODESPAR_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"reason": "string"
}),
});
const data = await res.json();const r = await cs.api.response("post", "/v1/orgs/{orgId}/pause", {
path: {
orgId: "org_0000000000000000"
},
body: {
reason: "string"
}
});
// r.status is one of the documented statuses (200, 403),
// each with its own body shape in r.data; nothing here throws on 403.
if (r.ok) {
console.log(r.data);
}{
"changed": true,
"pause": {
"org_id": "org_0000000000000000",
"paused": true,
"paused_at": "2026-01-15T12:00:00.000Z",
"reason": "string",
"paused_by": "string",
"paused_by_source": "bearer_key",
"resumed_at": "2026-01-15T12:00:00.000Z",
"generation": 0
}
}Commerce Memory
12 operations under /v1/commerce-memory (GET POST): parameters, status codes, refusal bodies, and the same request in curl, HTTP, Python and TypeScript.
Organizations
1 operation under /v1/organizations/{id} (GET): parameters, status codes, refusal bodies, and the same request in curl, HTTP, Python and TypeScript.