Skip to content

The contract your agent reports against

Every request an agent can make to Toolcaise, what it accepts and answers, and its limits, rendered from the OpenAPI 3.1 document the API serves.

Basics

Base URL https://toolcaise.com. Every agent request carries the agent's credential as Authorization: Bearer tca1...; it identifies the workspace and the agent, so neither is ever sent in a body. Create one per agent in the dashboard under Agents; it is shown once and can be rotated or revoked there. Requests are outbound HTTPS from the agent; nothing connects to your hosts.

EndpointBody limitRate limit
/api/v1/agents/telemetry128,000 bytes60 a minute per credential, shared with metrics and supervision
/api/v1/agents/metrics64,000 bytesShared, as above
/api/v1/agents/supervision16,000 bytesShared, as above
/api/v1/agents/control24,576 bytesIts own budget, so reporting can never starve a pause or cancel
/api/v1/otlp/v1/traces4 MiB, 16 MiB after gzipIts own budget

JSON bodies need Content-Type: application/json. Errors are JSON objects with one field, error, that says what was wrong. Retry network failures, 429 and 503 after the Retry-After delay with the same IDs: runs, spans, deliveries and checks are updated in place and events, outcomes and samples are stored once, so a retry never duplicates anything. Timestamps are ISO 8601 with a time zone.

Everything an agent sends is stored as agent-reported: a claim, not independent proof. Records sent with a credential Toolcaise Connect enrolled for a supervised run or MCP gateway are stored as measured by Connect instead; the label follows the credential, never a field in the request. Send operational metadata only. Fields named like secrets, prompts or content are refused; free text such as event messages is stored as written.

Reporting

What the agent did.

POST/api/v1/agents/telemetry

Report a heartbeat, run, events, spans or outcomes

One JSON report of up to 128,000 bytes. It is all or nothing: one invalid entry refuses the whole report with 400. Spans and outcomes must name a run this agent has reported, in the same request or an earlier one. Retrying with the same IDs is safe: events and outcomes are stored once, runs and deliveries are updated. Telemetry, metrics and supervision requests share 60 requests a minute per credential; beyond that the answer is 429 with Retry-After: 60.

FieldTypeDetails
version1required
status"healthy" | "degraded" | "offline" | "disabled"optionalThe agent's own view of its health. A report without it says healthy. Default "healthy".
heartbeatAtstring (date-time)optionalDefaults to the time Toolcaise receives the report. At most 10 minutes old and at most 1 minute ahead.
hostNamestringoptionalWhere the agent runs. A report without it records "Unreported". 1 to 160 characters.
unitNamestring or nulloptionalThe service or unit name. 1 to 200 characters.
releaseVersionstring or nulloptionalThe agent's release. 1 to 120 characters.
capabilitiesarray of stringoptionalDescriptive labels. They grant nothing. At most 24 items.
scheduleSchedule or nulloptionalThe schedule the agent runs on, so an overdue run can be flagged.
schedule.namestringrequired1 to 120 characters.
schedule.calendarstringrequiredFor example a cron expression or "daily 06:00". 1 to 180 characters.
schedule.timezonestringrequiredAn IANA time zone. 1 to 80 characters.
schedule.enabledbooleanoptional
schedule.activebooleanoptional
schedule.nextRunAtstring (date-time) or nulloptionalThe next expected run.
schedule.lastTriggeredAtstring (date-time) or nulloptionalThe last time it fired.
runRun or nulloptionalA snapshot of one run. Send it at the start (status running) and again when it ends; later snapshots update the same run.
run.externalRunIdstringrequiredYour ID for the run; reuse it in every report about the run. 2 to 120 letters, digits, dots, underscores, colons or hyphens.
run.status"running" | "completed" | "failed" | "held" | "skipped" | "shadow_passed" | "canceled"required
run.modestringrequiredHow the run executes, for example "observed". 1 to 40 characters.
run.triggerstringrequiredWhat started it, for example "application" or "schedule". 1 to 40 characters.
run.startedAtstring (date-time)requiredWhen the run started. Historical values are accepted.
run.completedAtstring (date-time) or nulloptionalWhen the run ended.
run.phasestring or nulloptional1 to 120 characters.
run.releaseVersionstring or nulloptional1 to 120 characters.
run.summaryobjectoptionalShort structured facts about the run, such as a label. At most 12,000 bytes as JSON. A key anywhere inside that contains secret, token, password, authorization, cookie, credential, prompt, html, content, extract, body or private key is refused.
run.countsobjectoptionalNamed counters. Keys follow the external ID format; values are integers from 0 to 1,000,000.
run.errorCodestring or nulloptional1 to 120 characters.
run.artifactDigeststring or nulloptional
run.durationMsinteger or nulloptional0 to 86,400,000.
eventsarray of EventoptionalAt most 100 items.
events[].externalEventIdstringrequiredYour ID for the event; a repeated ID is stored once. 2 to 120 letters, digits, dots, underscores, colons or hyphens.
events[].externalRunIdstring or nulloptionalThe run it belongs to, if any. 2 to 120 letters, digits, dots, underscores, colons or hyphens.
events[].eventTypestringrequiredA dotted type such as report.sent, in the external ID format. Types starting toolcaise. are reserved.
events[].level"info" | "warning" | "error"required
events[].messagestringrequiredA short, safe description. Never prompts, output or personal data. 1 to 600 characters.
events[].phasestring or nulloptional1 to 120 characters.
events[].detailobjectoptionalStructured detail. At most 12,000 bytes as JSON. A key anywhere inside that contains secret, token, password, authorization, cookie, credential, prompt, html, content, extract, body or private key is refused.
events[].occurredAtstring (date-time) or nulloptionalDefaults to the time Toolcaise receives it.
deliveriesarray of DeliveryoptionalAt most 24 items.
deliveries[].externalDeliveryIdstringrequiredYour ID for the delivery; later reports update it. 2 to 120 letters, digits, dots, underscores, colons or hyphens.
deliveries[].externalRunIdstring or nulloptionalThe run it belongs to, if any. 2 to 120 letters, digits, dots, underscores, colons or hyphens.
deliveries[].channelstringrequiredFor example email or webhook. 2 to 120 letters, digits, dots, underscores, colons or hyphens.
deliveries[].status"pending" | "sent" | "failed" | "skipped"required
deliveries[].recipientLabelstring or nulloptional1 to 160 characters.
deliveries[].errorCodestring or nulloptional1 to 120 characters.
deliveries[].attemptedAtstring (date-time) or nulloptionalDefaults to the time Toolcaise receives it.
deliveries[].sentAtstring (date-time) or nulloptionalWhen it was sent.
deliveries[].detailobjectoptionalStructured detail. At most 12,000 bytes as JSON. A key anywhere inside that contains secret, token, password, authorization, cookie, credential, prompt, html, content, extract, body or private key is refused.
spansarray of SpanoptionalAt most 50 items.
spans[].externalRunIdstringrequiredA run this agent has reported, in this request or earlier. No line breaks or control characters; credential-like strings and email addresses are replaced with [redacted]. 1 to 160 characters.
spans[].externalSpanIdstringrequiredYour ID for the span. No line breaks or control characters; credential-like strings and email addresses are replaced with [redacted]. 1 to 160 characters.
spans[].parentSpanIdstringoptionalThe enclosing span, if any. A span cannot parent itself. No line breaks or control characters; credential-like strings and email addresses are replaced with [redacted]. 1 to 160 characters.
spans[].namestringrequiredWhat the step is. No line breaks or control characters; credential-like strings and email addresses are replaced with [redacted]. 1 to 160 characters.
spans[].kind"step" | "tool" | "model" | "retry"required
spans[].status"running" | "succeeded" | "failed" | "cancelled"required
spans[].startedAtstring (date-time)requiredAt most one minute in the future.
spans[].completedAtstring (date-time)optionalNot before startedAt; at most one minute in the future.
spans[].toolstringoptionalThe tool used. No line breaks or control characters; credential-like strings and email addresses are replaced with [redacted]. 1 to 160 characters.
spans[].modelstringoptionalThe model used. No line breaks or control characters; credential-like strings and email addresses are replaced with [redacted]. 1 to 160 characters.
spans[].inputTokensintegeroptional0 to 1,000,000,000.
spans[].outputTokensintegeroptional0 to 1,000,000,000.
spans[].reportedCostUsdnumberoptional0 to 1,000,000,000.
spans[].metadataobjectoptionalUp to 16 numbers or booleans. Keys must match ^[a-zA-Z][a-zA-Z0-9_]{0,39}$; keys containing secret, token, password, key, prompt, input, output, body or content, and values of other types, are dropped rather than refused. At most 16 entries.
outcomesarray of OutcomeoptionalAt most 20 items.
outcomes[].externalRunIdstringrequiredA run this agent has reported, in this request or earlier. No line breaks or control characters; credential-like strings and email addresses are replaced with [redacted]. 1 to 160 characters.
outcomes[].externalOutcomeIdstringrequiredYour ID for the outcome; a repeated ID is stored once. No line breaks or control characters; credential-like strings and email addresses are replaced with [redacted]. 1 to 160 characters.
outcomes[].labelstringrequiredWhat was produced. No line breaks or control characters; credential-like strings and email addresses are replaced with [redacted]. 1 to 160 characters.
outcomes[].reportedStatus"succeeded" | "failed" | "unknown"required
outcomes[].artifactDigeststringoptionalSHA-256 of the artifact.

Refused with 400: orgId, agentId, agent.

StatusMeaning
200Stored.
400The body is not valid for this endpoint; error says which field.
401Missing, malformed or revoked agent credential.
413The body is larger than 128,000 bytes.
415Content-Type is not application/json.
429Telemetry, metrics and supervision requests share 60 requests a minute per credential; beyond that the answer is 429 with Retry-After: 60.
503Storage is temporarily unavailable. Retry after the Retry-After delay with the same IDs.
Example: A completed run
{
  "version": 1,
  "status": "healthy",
  "hostName": "worker-3",
  "releaseVersion": "2026.09.1",
  "run": {
    "externalRunId": "run-2026-09-22-0600",
    "status": "completed",
    "mode": "observed",
    "trigger": "schedule",
    "startedAt": "2026-09-22T06:00:00Z",
    "completedAt": "2026-09-22T06:04:12Z",
    "durationMs": 252000,
    "summary": {
      "label": "Daily research digest"
    },
    "counts": {
      "sources": 14
    }
  },
  "events": [
    {
      "externalEventId": "run-2026-09-22-0600:sent",
      "externalRunId": "run-2026-09-22-0600",
      "eventType": "report.sent",
      "level": "info",
      "message": "Digest sent to the research channel",
      "occurredAt": "2026-09-22T06:04:10Z"
    }
  ],
  "spans": [
    {
      "externalRunId": "run-2026-09-22-0600",
      "externalSpanId": "summarize-1",
      "name": "Summarize sources",
      "kind": "model",
      "status": "succeeded",
      "startedAt": "2026-09-22T06:01:02Z",
      "completedAt": "2026-09-22T06:01:30Z",
      "model": "gpt-4.1",
      "inputTokens": 1200,
      "outputTokens": 350,
      "reportedCostUsd": 0.012,
      "metadata": {
        "latencyMs": 28000,
        "streamed": false
      }
    }
  ],
  "outcomes": [
    {
      "externalRunId": "run-2026-09-22-0600",
      "externalOutcomeId": "digest",
      "label": "Daily digest",
      "reportedStatus": "succeeded"
    }
  ]
}
Example: A heartbeat
{
  "version": 1
}
curl: A completed run
curl --fail-with-body https://toolcaise.com/api/v1/agents/telemetry \
  -H "Authorization: Bearer $TOOLCAISE_AGENT_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"version":1,"status":"healthy","hostName":"worker-3","releaseVersion":"2026.09.1","run":{"externalRunId":"run-2026-09-22-0600","status":"completed","mode":"observed","trigger":"schedule","startedAt":"2026-09-22T06:00:00Z","completedAt":"2026-09-22T06:04:12Z","durationMs":252000,"summary":{"label":"Daily research digest"},"counts":{"sources":14}},"events":[{"externalEventId":"run-2026-09-22-0600:sent","externalRunId":"run-2026-09-22-0600","eventType":"report.sent","level":"info","message":"Digest sent to the research channel","occurredAt":"2026-09-22T06:04:10Z"}],"spans":[{"externalRunId":"run-2026-09-22-0600","externalSpanId":"summarize-1","name":"Summarize sources","kind":"model","status":"succeeded","startedAt":"2026-09-22T06:01:02Z","completedAt":"2026-09-22T06:01:30Z","model":"gpt-4.1","inputTokens":1200,"outputTokens":350,"reportedCostUsd":0.012,"metadata":{"latencyMs":28000,"streamed":false}}],"outcomes":[{"externalRunId":"run-2026-09-22-0600","externalOutcomeId":"digest","label":"Daily digest","reportedStatus":"succeeded"}]}'

POST/api/v1/agents/metrics

Report usage and resource samples, and output checks

Up to 30 samples and 30 output checks per request, 64,000 bytes at most. Unknown top-level fields are refused. Samples and checks that name a run need that run reported first. Call-scope samples are per model call and run-scope samples are cumulative totals, so the two are never added together. Telemetry, metrics and supervision requests share 60 requests a minute per credential; beyond that the answer is 429 with Retry-After: 60.

FieldTypeDetails
version1required
samplesarray of MetricSampleoptionalAt most 30 items.
samples[].externalSampleIdstringrequiredYour ID for the sample; a repeated ID is stored once. Up to 200 characters, no control characters or credentials. 1 to 200 characters.
samples[].externalRunIdstring or nulloptionalA run this agent has reported. Required for run and call scope. Up to 200 characters, no control characters or credentials. 1 to 200 characters.
samples[].scope"host" | "run" | "call"requiredhost: the machine or process. run: cumulative totals for a run. call: one model call, identified by callId.
samples[].callIdstring or nulloptionalRequired for call scope; reuse it when retrying. Up to 200 characters, no control characters or credentials. 1 to 200 characters.
samples[].recordedAtstring (date-time)requiredAt most one minute ahead and no older than 31 days.
samples[].tagsobjectoptional
samples[].tags.versionstringoptionalYour agent or prompt version. Up to 100 characters, no control characters or credentials. 1 to 100 characters.
samples[].tags.promptstringoptionalA prompt template name, never the prompt. Up to 100 characters, no control characters or credentials. 1 to 100 characters.
samples[].tags.modelstringoptionalThe model. Up to 100 characters, no control characters or credentials. 1 to 100 characters.
samples[].valuesobjectrequiredAt least one measurement. null means unknown, never zero.
samples[].values.contextTokensinteger or nulloptional0 to 1,000,000,000,000,000.
samples[].values.contextCapacityinteger or nulloptional0 to 1,000,000,000,000,000. More than 0.
samples[].values.inputTokensinteger or nulloptional0 to 1,000,000,000,000,000.
samples[].values.outputTokensinteger or nulloptional0 to 1,000,000,000,000,000.
samples[].values.reportedCostUsdnumber or nulloptional0 to 1,000,000,000.
samples[].values.ramBytesinteger or nulloptional0 to 1,000,000,000,000,000.
samples[].values.cpuPercentnumber or nulloptional0 to 100.
samples[].values.diskFreeBytesinteger or nulloptional0 to 1,000,000,000,000,000.
samples[].values.diskUsedBytesinteger or nulloptional0 to 1,000,000,000,000,000.
samples[].values.outputBytesinteger or nulloptional0 to 1,000,000,000,000,000.
samples[].values.artifactBytesinteger or nulloptional0 to 1,000,000,000,000,000.
samples[].values.workloadItemsinteger or nulloptional0 to 1,000,000,000,000,000.
samples[].values.runtimeMsnumber or nulloptional0 to 1,000,000,000,000,000.
samples[].values.retryCountinteger or nulloptional0 to 1,000,000,000,000,000.
samples[].values.stepCountinteger or nulloptional0 to 1,000,000,000,000,000.
samples[].values.childCountinteger or nulloptional0 to 1,000,000,000,000,000.
checksarray of OutputCheckoptionalAt most 30 items.
checks[].externalCheckIdstringrequiredYour ID for the check; a newer result replaces an older one. Up to 200 characters, no control characters or credentials. 1 to 200 characters.
checks[].externalRunIdstring or nulloptionalA run this agent has reported. Up to 200 characters, no control characters or credentials. 1 to 200 characters.
checks[].namestringrequiredWhat was checked. Up to 160 characters, no control characters or credentials. 1 to 160 characters.
checks[].kind"exists" | "nonempty" | "schema" | "freshness"required
checks[].passedboolean or nullrequirednull means the check could not tell.
checks[].recordedAtstring (date-time)requiredAt most one minute ahead.
checks[].reason"missing" | "empty" | "invalid_json" | "schema_mismatch" | "stale" | "unreadable" | "too_large" | "not_regular_file" | "unsupported_schema" | "verified" or nulloptional
StatusMeaning
200Stored.
400The body is not valid for this endpoint; error says which field.
401Missing, malformed or revoked agent credential.
413The body is larger than 64,000 bytes.
415Content-Type is not application/json.
429Telemetry, metrics and supervision requests share 60 requests a minute per credential; beyond that the answer is 429 with Retry-After: 60.
503Storage is temporarily unavailable. Retry with the same sample IDs.
Example: A call sample and a check
{
  "version": 1,
  "samples": [
    {
      "externalSampleId": "run-2026-09-22-0600:summarize-1:usage",
      "externalRunId": "run-2026-09-22-0600",
      "scope": "call",
      "callId": "summarize-1",
      "recordedAt": "2026-09-22T06:01:30Z",
      "tags": {
        "model": "gpt-4.1",
        "prompt": "summary-v3"
      },
      "values": {
        "inputTokens": 1200,
        "outputTokens": 350,
        "reportedCostUsd": 0.012
      }
    }
  ],
  "checks": [
    {
      "externalCheckId": "daily-report:exists",
      "externalRunId": "run-2026-09-22-0600",
      "name": "Daily report",
      "kind": "exists",
      "passed": true,
      "reason": "verified",
      "recordedAt": "2026-09-22T06:04:00Z"
    }
  ]
}
curl: A call sample and a check
curl --fail-with-body https://toolcaise.com/api/v1/agents/metrics \
  -H "Authorization: Bearer $TOOLCAISE_AGENT_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"version":1,"samples":[{"externalSampleId":"run-2026-09-22-0600:summarize-1:usage","externalRunId":"run-2026-09-22-0600","scope":"call","callId":"summarize-1","recordedAt":"2026-09-22T06:01:30Z","tags":{"model":"gpt-4.1","prompt":"summary-v3"},"values":{"inputTokens":1200,"outputTokens":350,"reportedCostUsd":0.012}}],"checks":[{"externalCheckId":"daily-report:exists","externalRunId":"run-2026-09-22-0600","name":"Daily report","kind":"exists","passed":true,"reason":"verified","recordedAt":"2026-09-22T06:04:00Z"}]}'

Controls

Decisions the agent waits for. They fail closed.

POST/api/v1/agents/control

Poll for commands, acknowledge them, and request and use approvals

Cooperative controls: the agent asks at checkpoints it chooses, so nothing here can interrupt work already in progress. Any failure means do not proceed: an unreachable server, an error, or an uncertain consume_approval response must leave the guarded action unexecuted. Bodies are at most 24,576 bytes. Control has its own limit of 300 requests a minute per credential per server process, so a busy reporter cannot starve a pause or a cancel.

op: "poll"

Ask for commands at a checkpoint, declaring what this runner can apply; only those commands are delivered. With runId, the run must already be reported.

FieldTypeDetails
op"poll"required
capabilitiesarray of "pause" | "resume" | "cancel" | "approvals" | "policy"requiredAt most 5 items.
runIdstring or nulloptionalThe run at this checkpoint. Up to 200 characters, no control characters. 1 to 200 characters.
policyVersioninteger or nulloptionalWith the policy capability: the policy version this runner enforces now. 1 to 2,000,000,000.
policyRejectedobject or nulloptional
policyRejected.versionintegerrequired1 to 2,000,000,000.
policyRejected.reasonstringrequiredWhy the runner could not apply it. Up to 200 characters, no control characters. 1 to 200 characters.

op: "ack"

Confirm that a delivered command was applied.

FieldTypeDetails
op"ack"required
commandIdstring (uuid)required
appliedState"paused" | "running" | "cancelled"requiredpaused for pause, running for resume, cancelled for cancel.

op: "request_approval"

Ask a person to approve one exact action. Toolcaise stores the digest and the title, never the action itself; the optional reason, destination, affectedCount and permittedFields tell the person more. Those are shown as reported by the agent: Toolcaise cannot check them against the digest. In every text, credential-like strings (bearer tokens, API keys, private keys, password= pairs) and email addresses are replaced with [redacted], and a permitted field whose name suggests a secret (password, token, credential, cookie, or a name ending in key, such as api_key or sshKey) shows [redacted] as its value. Repeating a requestId returns the same approval; repeating it with anything different is refused with 409.

FieldTypeDetails
op"request_approval"required
runIdstringrequiredA run this agent has reported. Up to 200 characters, no control characters. 1 to 200 characters.
actionDigeststringrequiredSHA-256 of the canonical JSON of the exact action. Lowercase hexadecimal SHA-256.
requestIdstring (uuid)required
titlestringrequiredWhat the person is approving, safe to show. Up to 160 characters, no control characters. 1 to 160 characters.
expiresInSecondsintegeroptional60 to 86,400. Default 3600.
reasonstring or nulloptionalWhy a person must approve it, for example the policy rule that matched. Up to 200 characters, no control characters. 1 to 200 characters.
destinationstring or nulloptionalThe system, host or record type the action writes to. Up to 120 characters, no control characters. 1 to 120 characters.
affectedCountinteger or nulloptionalHow many records, messages or people the action affects, as the agent counts them. 0 to 1,000,000,000.
permittedFieldsarray of object or nulloptionalThe action's parameters the agent chooses to show the person, as text. Send only what the decision needs; the rest of the action never leaves the agent. At most 12 items.
permittedFields.namestringrequiredThe parameter's name. Up to 40 characters, no control characters. 1 to 40 characters.
permittedFields.valuestringrequiredThe parameter's value as text, up to 200 characters, no control characters. Up to 200 characters.

op: "approval_status"

Read the state of an approval.

FieldTypeDetails
op"approval_status"required
runIdstringrequiredThe run the approval is bound to. Up to 200 characters, no control characters. 1 to 200 characters.
actionDigeststringrequiredThe digest the approval is bound to. Lowercase hexadecimal SHA-256.
approvalIdstring (uuid)required

op: "consume_approval"

Use an approval, once, immediately before executing the action. Refused while the agent or run is paused or cancelled. Never retry an uncertain response: treat it as not authorized.

FieldTypeDetails
op"consume_approval"required
runIdstringrequiredThe run the approval is bound to. Up to 200 characters, no control characters. 1 to 200 characters.
actionDigeststringrequiredThe digest the approval is bound to. Lowercase hexadecimal SHA-256.
approvalIdstring (uuid)required

Refused with 400: orgId, agentId.

StatusMeaning
200Done.
400The body is not valid for this endpoint; error says which field.
401Missing, malformed or revoked agent credential.
404The run has not been reported yet, or the command or approval does not exist for this agent.
409Not allowed in the current state: approval support not declared at a checkpoint, the approval is bound to another action or is pending, denied, expired or used, the agent or run is paused or cancelled, or 100 approvals are already pending.
413The body is larger than 24,576 bytes.
415Content-Type is not application/json.
429More than 300 control requests a minute for this credential. Retry-After says how long to wait.
503Control storage is unavailable. Do not execute unapproved actions.
Example: poll
{
  "op": "poll",
  "runId": "run-2026-09-22-0600",
  "capabilities": [
    "pause",
    "resume",
    "cancel",
    "approvals"
  ]
}
Example: ack
{
  "op": "ack",
  "commandId": "5f0f3c1e-8a1b-4c2d-9e3f-0a1b2c3d4e5f",
  "appliedState": "paused"
}
Example: request_approval
{
  "op": "request_approval",
  "runId": "run-2026-09-22-0600",
  "actionDigest": "9f2c7a0e4b1d3c5a6e8f0b2d4c6a8e0f1b3d5c7a9e1f3b5d7c9a1e3f5b7d9c1a",
  "requestId": "1b4e28ba-2fa1-41d2-883f-0016d3cca427",
  "title": "Send the digest to 1,240 subscribers",
  "expiresInSeconds": 3600,
  "reason": "Sends to more than 1,000 people need a person's approval",
  "destination": "Newsletter list: subscribers",
  "affectedCount": 1240,
  "permittedFields": [
    {
      "name": "list",
      "value": "subscribers"
    },
    {
      "name": "draft",
      "value": "digest-2026-09-22"
    }
  ]
}
Example: approval_status
{
  "op": "approval_status",
  "runId": "run-2026-09-22-0600",
  "actionDigest": "9f2c7a0e4b1d3c5a6e8f0b2d4c6a8e0f1b3d5c7a9e1f3b5d7c9a1e3f5b7d9c1a",
  "approvalId": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
}
Example: consume_approval
{
  "op": "consume_approval",
  "runId": "run-2026-09-22-0600",
  "actionDigest": "9f2c7a0e4b1d3c5a6e8f0b2d4c6a8e0f1b3d5c7a9e1f3b5d7c9a1e3f5b7d9c1a",
  "approvalId": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
}
curl: poll
curl --fail-with-body https://toolcaise.com/api/v1/agents/control \
  -H "Authorization: Bearer $TOOLCAISE_AGENT_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"op":"poll","runId":"run-2026-09-22-0600","capabilities":["pause","resume","cancel","approvals"]}'

Supervision workflows

Recovery, impact, sources and handoffs.

GET/api/v1/agents/supervision

Read this agent's workflow items

Telemetry, metrics and supervision requests share 60 requests a minute per credential; beyond that the answer is 429 with Retry-After: 60.

StatusMeaning
200The snapshot.
401Missing, malformed or revoked agent credential.
429Telemetry, metrics and supervision requests share 60 requests a minute per credential; beyond that the answer is 429 with Retry-After: 60.
503Supervision storage is unavailable.

POST/api/v1/agents/supervision

Report and advance workflow items

The operations an agent may perform. Reviews, recovery authorizations, assignments, source invalidation and correction, effort, offboarding and drills are human operations and are refused with 403 for an agent credential. Items carry a version: send it back as expectedVersion; a 409 means the item changed. Bodies are at most 16,000 bytes. Telemetry, metrics and supervision requests share 60 requests a minute per credential; beyond that the answer is 429 with Retry-After: 60.

op: "create"

Report a workflow item. recovery_step, impact and output need a reported run. Repeating a requestId returns the existing item.

FieldTypeDetails
op"create"required
kind"recovery_step" | "impact" | "obligation" | "source" | "output"required
runIdstring or nulloptionalA run this agent has reported. Up to 120 characters, no control characters, credentials or email addresses. 1 to 120 characters.
titlestringrequiredWhat the item is. Up to 160 characters, no control characters, credentials or email addresses. 1 to 160 characters.
requestIdstring (uuid)required
dataobjectoptionalDepends on kind: see the matching schema below.

data when kind is recovery_step (runId required)

FieldTypeDetails
stepKeystringrequiredWhich step of the run to recover. Up to 80 characters, no control characters, credentials or email addresses. 1 to 80 characters.
actionDigeststringrequiredThe exact recovery action. Lowercase hexadecimal SHA-256.
evidenceDigeststring or nulloptionalEvidence gathered for review. Lowercase hexadecimal SHA-256.
proposedClassification"verified_complete" | "safe_to_retry" | "needs_approval" | "cannot_verify"optionalDefault "cannot_verify".
effect"none" | "read" | "write" | "unknown"optionalDefault "unknown".

data when kind is impact (runId required)

FieldTypeDetails
actionDigeststringrequiredThe action whose impact this is. Lowercase hexadecimal SHA-256.
beforeobjectoptionalUp to 20 numbers or booleans. Names containing secret, token, password, email, prompt, content, payload or credential are refused. At most 20 entries.
afterobjectoptionalUp to 20 numbers or booleans. Names containing secret, token, password, email, prompt, content, payload or credential are refused. At most 20 entries.
affectedCountintegeroptional0 to 1,000,000,000. Default 0.
reversibility"reversible" | "partial" | "irreversible" | "unknown"optionalDefault "unknown".
dependenciesarray of string (uuid)optionalAt most 24 items.

data when kind is output (runId required)

FieldTypeDetails
artifactDigeststringrequiredThe artifact produced. Lowercase hexadecimal SHA-256.
outcomeIdstring (uuid) or nulloptionalA reported outcome of the same run.

data when kind is source

FieldTypeDetails
sourceKeystringrequiredWhich source. Up to 160 characters, no control characters, credentials or email addresses. 1 to 160 characters.
contentDigeststringrequiredThe content the agent used. Lowercase hexadecimal SHA-256.

data when kind is obligation

FieldTypeDetails
dueAtstring (date-time) or nulloptional
handoffCodestringoptionalWhat happens next. Up to 160 characters, no control characters, credentials or email addresses. 1 to 160 characters. Default "review_required".

op: "claim_recovery"

Claim a recovery step before running it: one a person authorized, or one whose effect is read or none, which needs no authorization. Single use. With an idempotencyKey, repeating the same request returns the original claim instead of a second grant, so an uncertain response can be retried with the same key; without one, never retry an uncertain response.

FieldTypeDetails
op"claim_recovery"required
idstring (uuid)required
expectedVersionintegerrequired0 to 1,000,000,000.
actionDigeststringrequiredThe exact action that was authorized. Lowercase hexadecimal SHA-256.
idempotencyKeystringoptionalChosen by the runner, and kept until the result is reported: a UUID, or letters, digits and . _ : -. Unique per agent. The same key with the same id and actionDigest returns the same claim while it is open, with expectedVersion either the version claimed or the claim's itemVersion. A key that looks like a credential (a word starting tca1., sk_live_, sk_test_, or sk- and 12 letters or digits) is refused. 8 to 128 characters.

op: "recovery_result"

FieldTypeDetails
op"recovery_result"required
idstring (uuid)required
expectedVersionintegerrequired0 to 1,000,000,000.
result"succeeded" | "failed"required
evidenceDigeststring or nulloptionalEvidence of the result. Lowercase hexadecimal SHA-256.

op: "observe_job"

Report what a job actually used, to compare with what its definition allows.

FieldTypeDetails
op"observe_job"required
jobIdstring (uuid)required
requestIdstring (uuid)required
toolsarray of stringoptionalTools used. At most 24 items.
systemsarray of stringoptionalSystems touched. At most 24 items.
actionsarray of stringoptionalActions taken. At most 24 items.
runIdstring or nulloptionalThe run. Up to 120 characters, no control characters, credentials or email addresses. 1 to 120 characters.

op: "link"

FieldTypeDetails
op"link"required
fromIdstring (uuid)required
toIdstring (uuid)required
relation"derived_from" | "depends_on" | "handoff"required

op: "report_handoff"

Report progress on an obligation assigned to this agent.

FieldTypeDetails
op"report_handoff"required
idstring (uuid)required
expectedVersionintegerrequired0 to 1,000,000,000.
state"in_progress" | "reported_complete" | "blocked"required
evidenceDigeststring or nulloptionalEvidence of the progress. Lowercase hexadecimal SHA-256.

Refused with 400: agentId, orgId, actor.

StatusMeaning
200Done.
400The body is not valid for this endpoint; error says which field.
401Missing, malformed or revoked agent credential.
403A human operation, or a job or offboarding item, sent by an agent.
404The item, outcome or assigned handoff does not belong to this agent.
409The item changed (stale expectedVersion), or the operation is not allowed in its state.
413The body is larger than 16,000 bytes.
415Content-Type is not application/json.
429Telemetry, metrics and supervision requests share 60 requests a minute per credential; beyond that the answer is 429 with Retry-After: 60.
503Supervision storage is unavailable. Do not execute an unconfirmed recovery.
Example: create
{
  "op": "create",
  "kind": "recovery_step",
  "runId": "run-2026-09-22-0600",
  "title": "Resend the digest to the failed batch",
  "requestId": "a8098c1a-f86e-41d2-a0a3-b3c4d5e6f708",
  "data": {
    "stepKey": "send-batch-7",
    "actionDigest": "3a7bd3e2360a3d29eea436fcfb7e44c735d117c42d1c1835420b6b9942dd4f1b",
    "proposedClassification": "safe_to_retry",
    "effect": "write"
  }
}
Example: claim_recovery
{
  "op": "claim_recovery",
  "id": "0b4c2f1e-3d5a-4b6c-8d7e-9f0a1b2c3d4e",
  "expectedVersion": 3,
  "actionDigest": "3a7bd3e2360a3d29eea436fcfb7e44c735d117c42d1c1835420b6b9942dd4f1b",
  "idempotencyKey": "0f8fad5b-d9cb-469f-a165-70867728950e"
}
Example: recovery_result
{
  "op": "recovery_result",
  "id": "0b4c2f1e-3d5a-4b6c-8d7e-9f0a1b2c3d4e",
  "expectedVersion": 4,
  "result": "succeeded"
}
Example: observe_job
{
  "op": "observe_job",
  "jobId": "c56a4180-65aa-42ec-a945-5fd21dec0538",
  "requestId": "e4eaaaf2-d142-41c8-9f0e-0a1b2c3d4e5f",
  "tools": [
    "web_search",
    "send_email"
  ],
  "systems": [
    "newsletter"
  ],
  "actions": [
    "send"
  ],
  "runId": "run-2026-09-22-0600"
}
Example: link
{
  "op": "link",
  "fromId": "0b4c2f1e-3d5a-4b6c-8d7e-9f0a1b2c3d4e",
  "toId": "c56a4180-65aa-42ec-a945-5fd21dec0538",
  "relation": "depends_on"
}
Example: report_handoff
{
  "op": "report_handoff",
  "id": "0b4c2f1e-3d5a-4b6c-8d7e-9f0a1b2c3d4e",
  "expectedVersion": 2,
  "state": "in_progress"
}
curl: create
curl --fail-with-body https://toolcaise.com/api/v1/agents/supervision \
  -H "Authorization: Bearer $TOOLCAISE_AGENT_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"op":"create","kind":"recovery_step","runId":"run-2026-09-22-0600","title":"Resend the digest to the failed batch","requestId":"a8098c1a-f86e-41d2-a0a3-b3c4d5e6f708","data":{"stepKey":"send-batch-7","actionDigest":"3a7bd3e2360a3d29eea436fcfb7e44c735d117c42d1c1835420b6b9942dd4f1b","proposedClassification":"safe_to_retry","effect":"write"}}'

OpenTelemetry

OTLP/HTTP trace export.

POST/api/v1/otlp/v1/traces

Export OpenTelemetry traces (OTLP/HTTP)

Point an OpenTelemetry trace exporter here (OTEL_EXPORTER_OTLP_TRACES_ENDPOINT) with the agent credential in OTEL_EXPORTER_OTLP_HEADERS. Each trace becomes a run; model, tool and agent-step spans are stored with the token counts the instrumentation reported. Only an allow-list of GenAI, OpenInference, OpenLLMetry and Vercel AI SDK attributes is read, so prompts, completions and tool arguments are never decoded. Bodies are at most 4 MiB as sent and 16 MiB after gzip, and at most 2000 spans are stored per request. 600 requests a minute per credential per server process, separate from the JSON API's 60. Observation only: this path cannot pause, cancel or approve.

Body: an OTLP ExportTraceServiceRequest, as application/x-protobuf or application/json, optionally gzip compressed. See the framework quickstarts for exporter settings.

StatusMeaning
200Accepted. JSON requests get JSON back and protobuf requests get protobuf; partialSuccess counts spans that were not stored.
400Malformed protobuf, JSON or gzip. Exporters do not retry this.
401Missing, malformed or revoked agent credential.
413The body is too large. Lower the exporter's batch size.
415Use application/x-protobuf or application/json, with gzip or no encoding.
429Too many export requests for this credential. Retry-After says how long to wait.
503Storage is unavailable. Exporters retry; writes are idempotent. Retry-After says how long to wait.

POST/api/v1/otlp/v1/metrics

Not supported: OpenTelemetry metrics

Toolcaise ingests traces only. Set OTEL_METRICS_EXPORTER=none. This answers 501 rather than accepting and discarding data.

StatusMeaning
501Metrics are not ingested.

POST/api/v1/otlp/v1/logs

Not supported: OpenTelemetry logs

Toolcaise ingests traces only. Set OTEL_LOGS_EXPORTER=none. This answers 501 rather than accepting and discarding data.

StatusMeaning
501Logs are not ingested.

Meta

This description.

GET/api/v1/openapi.json

This OpenAPI description

No credential needed.

StatusMeaning
200The OpenAPI 3.1 document.