Quickstart: add approvals to an AI agent in minutes
Create your first Contro1 request, route it to the right operator, and return a signed callback to your agent.
Use this quickstart when you want to add a human approval step to an existing agent without redesigning the whole workflow.
Key takeaways
A Contro1 request is a single POST to https://api.contro1.com/api/centcom/v1/requests with a question, context, and callback URL.
The human answer arrives as a signed webhook you verify before resuming the agent.
Idempotency keys keep retries safe; required_role keeps the decision with the right human.
If the agent waits synchronously, set its wait timeout longer than the request SLA plus a small callback buffer.
Optional policy_context and approval_comment_required fields let any custom policy layer preserve why review was required.
Add correlation_id when this request belongs to a larger agent run or case timeline.
What you need
A Contro1 API key (get one in Settings -> APIs & Webhooks)
A callback URL your server controls - Contro1 will POST the signed decision there
A clear question, supporting context, and the role that should answer
How the approval loop works
Your agent creates a request when it reaches a risky or policy-sensitive action. CENTCOM routes it to the right human based on required_role, shift coverage, and priority.
If the agent polls or blocks while waiting, configure its local timeout to exceed the Contro1 SLA. For example, a 10 minute SLA should use a wait timeout above 10 minutes so the agent does not auto-cancel or detach before Contro1 can return timed_out or escalate.
The request moves through these states:
pending - created, not yet claimed by an operator
in_review - an operator opened and is reading the request
approved - operator approved; the signed callback is on its way to your server
rejected - operator rejected; your agent should halt or route differently
timed_out - no one answered within the SLA window; treat as rejected and fail closed
cancelled - you called DELETE on the request before it was answered
Create your first request
create_request.sh
curl -X POST https://api.contro1.com/api/centcom/v1/requests \
-H "Authorization: Bearer cc_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"type": "approval",
"question": "Approve refund over policy limit?",
"context": "Customer requests a $2,400 refund after a shipping failure.",
"callback_url": "https://your-app.com/webhooks/contro1",
"required_role": "manager",
"priority": "urgent",
"external_request_id": "refund-8842:approval",
"correlation_id": "case-refund-8842",
"metadata": { "customer_id": "c-8821" }
}'
create_request.py
from centcom import CentcomClient
client = CentcomClient(api_key="cc_live_your_key")
request = client.create_request(
type="approval",
question="Approve refund over policy limit?",
context="Customer requests a $2,400 refund after a shipping failure.",
callback_url="https://your-app.com/webhooks/contro1",
required_role="manager",
priority="urgent",
external_request_id="refund-8842:approval",
correlation_id="case-refund-8842",
metadata={"customer_id": "c-8821"},
)
print(request["id"], request["status"]) # req_abc123 pending
create_request.ts
import { CentcomClient } from '@contro1/sdk';
const client = new CentcomClient({ apiKey: 'cc_live_your_key' });
const request = await client.createRequest({
type: 'approval',
question: 'Approve refund over policy limit?',
context: 'Customer requests a $2,400 refund after a shipping failure.',
callback_url: 'https://your-app.com/webhooks/contro1',
required_role: 'manager',
priority: 'urgent',
external_request_id: 'refund-8842:approval',
correlation_id: 'case-refund-8842',
metadata: { customer_id: 'c-8821' },
});
console.log(request.id, request.status); // req_abc123 pending
Where that context line came from
The context in the example ("Customer requests a $2,400 refund after a shipping failure") is not something the reviewer should hope the agent volunteers. Your gate builds it from three sources: the exact tool input your code intercepted (machine-observed fact), the message or event that triggered the run, and the agent's own justification - which you get reliably by making reason a required parameter of the risky tool, so the model produces it at decision time.
Keep the two apart: facts your code observed versus text the model wrote. Agent-written justification helps the reviewer decide, but it must never change routing or risk_level, because a prompt-injected agent writes very persuasive reasons. See the Requests API reference for the full context pattern with provenance labels.
When an operator responds, Contro1 sends a signed POST to your callback_url. Always verify the signature and timestamp before applying the decision.
New handlers can usually read top-level status plus response. protocol_response and structured_response are included for protocol adapters and compatibility.
Once approvals work, add identity and traceability one rung at a time — you do not have to do it all at once. Bind your API key to an agent (every request is then attributed automatically), pass a trace_id to link a whole run, attach tool_calls[] and retrieved_context[] so reviewers see what the agent did and why, set least-agency scopes to bound its authority, and export an HMAC-signed evidence packet for any decision.
Identity — bind a key to an agent; verified vs claimed sub-agents.
Trace — trace_id (+ parent_trace_id) links every step of a run.
The signed webhook is cryptographic proof of a human decision. Verify it inside the system that executes the action - not inside the agent. Any tool that must never run without human sign-off (payments, deploys, data deletion) should refuse to act without a verified approval; that way no agent, including shadow agents nobody registered, can trigger it by skipping Contro1.
Verify the signature and reject timestamps older than 5 minutes (replay protection; the timestamp marks callback delivery, not request creation, so long SLAs are unaffected).
Bind the approval to the exact action parameters via metadata / correlation_id - never treat "an approval arrived" as permission for a different action.
Execute each request_id exactly once (idempotency on your side).
When in doubt, confirm state directly with GET /v1/requests/:id using a read-only API key.
Create a request when the action is high-impact, irreversible, financially sensitive, or policy-sensitive.
Can I use Contro1 without changing my whole stack?
Yes. The normal starting point is a single API call plus a callback handler around the risky action.
What happens if the operator does not answer in time?
The request expires with a timed_out status. Your workflow should fail closed by default and optionally route to a fallback approver.
Do I need a dedicated SDK?
No. Any HTTP client works. We publish framework-specific helpers (LangGraph, OpenAI Agents, CrewAI, n8n, Claude managed agents) so you do not have to glue the HTTP calls yourself.
A practical walkthrough of the Contro1 runtime API: which endpoint an agent calls, when to call it, what to send, and how approvals, audit records, traces, and evidence fit together.