Core API

Requests API reference for AI agent approvals

Learn how to create, inspect, and cancel Contro1 requests for approvals, clarifications, and escalations.

The Requests API is the core surface agents use to ask humans for approval, clarification, or escalation in production.

Key takeaways

  • Three interaction types cover most cases: approval (approve/reject, with a comment when policy requires it), yes_no, and free_text.
  • required_role narrows who can claim a request; metadata rides through to the callback for correlation.
  • risk_level, policy_trigger, policy_context, approval_comment_required, and approval_requirements are optional decision-context fields for audit-ready oversight and policy-engine integrations.
  • external_request_id gives you idempotency - the same key never creates two requests.
  • Keep the question short and decisive; put evidence in context.
  • Label context by provenance: machine-observed facts versus agent-reported justification. Agent-written text never changes routing or risk.

Request fields that matter most

  • type defines the interaction model: yes_no, free_text, or approval
  • question is the direct decision prompt shown to the operator
  • context gives the supporting facts a human needs to answer quickly
  • required_role narrows who can claim or answer the request
  • metadata returns workflow correlation data in the callback
  • risk_level, policy_trigger, policy_context, and approval_requirements explain why this human decision is required
  • external_request_id is the idempotency key - retries with the same value return the original request
  • correlation_id groups related requests and audit records into one case timeline

Base URL

Use the API host for runtime traffic. The canonical base URL is https://api.contro1.com/api/centcom/v1.

The marketing site remains at https://contro1.com, but production create/get/delete calls should target api.contro1.com.

Operational guidance

Keep the question short and decisive.

Put policy detail, transaction facts, or execution context in the context field so the operator can understand the risk without opening another system.

Send policy_trigger when your own policy logic decides a human must review. Send policy_context when any policy source, risk service, rules engine, or framework tells you why review is required. This can be Microsoft AGT, OPA, Cedar, a homegrown risk classifier, a YAML rule file, or simple application code.

Contro1 records the policy evidence, routes the human decision, enforces reviewer comment rules, and returns the signed outcome. It does not need to own your policy logic.

For US AI governance programs, these optional fields help preserve NIST-style risk management evidence without moving impact assessments, bias testing, notices, or legal classification into Contro1.

Treat timeouts as real outcomes. An unanswered request is not an approval - it is a decision your workflow must still handle.

Set the agent or SDK wait timeout longer than the request SLA plus callback/escalation buffer. If the request uses sla_minutes: 10, the agent should wait more than 10 minutes, otherwise the local run may auto-cancel before Contro1 reaches the SLA outcome.

Reviewer context: where the "why" comes from

A reviewer who sees "Approve transfer?" with nothing else cannot exercise judgment - they can only rubber-stamp. Every gated request should answer three questions before a human opens it: what exactly will run, why the agent wants to run it, and what happened before this moment. That context comes from four sources:

  • The exact action payload (machine-observed). Your gate copies the tool call input verbatim into context: amount, recipient, command, target environment. This comes from your code, not from the model, so the reviewer can treat it as fact.
  • The agent's justification (agent-reported). Make the reason a required parameter of the risky tool itself, for example transfer_money(to, amount, reason). The model produces the justification at decision time and it rides through the gate with the action. Asking "why" after the fact is unreliable; requiring it up front is not.
  • The originating trigger (machine-observed). Attach the user message or event that started the run, plus the last few tool calls from the same session. Hooks and middleware can read these from the transcript or framework state without asking the model anything.
  • History already in Contro1. Send correlation_id and thread_id so the reviewer opens the case timeline: what was already approved, asked, or logged in this same case.
reviewer-context.json
{
  "type": "approval",
  "question": "Approve $12,400 transfer to acct_889?",
  "context": {
    "action": {
      "tool": "transfer_money",
      "input": { "to": "acct_889", "amount_usd": 12400 }
    },
    "machine_observed": {
      "triggered_by": "Support ticket #5521: customer requests refund for order #1842",
      "recent_tool_calls": ["lookup_order", "check_refund_policy"]
    },
    "agent_reported": {
      "justification": "Refund qualifies under the shipping-failure exception policy."
    }
  }
}

Trust rules for agent-written context

Separate the two kinds of context and label them. Facts your gate observed (the payload, the triggering message, prior tool calls) are machine-observed. Text the model wrote (justification, summary) is agent-reported: useful to the reviewer, but generated by the same model that decided to act. A prompt-injected agent writes very persuasive justifications.

Two hard rules follow. First, agent-reported text must never change routing, risk_level, or approval policy - it informs the human and nothing else. Contro1 enforces this server-side: nothing inside agent_reported is read by routing, risk, or policy logic. Second, when a high-risk request arrives without its required context (no payload, no trigger, no justification), fail closed: reject it or bounce it back to the agent instead of asking a human to guess.

All three provenance keys (action, machine_observed, agent_reported) are optional. The smallest request still works with a plain string context; send the structured keys when you want the reviewer view, evidence packet, and Activity log to carry the provenance split. context.action is shorthand for tool_name/tool_input and is normalized into them on ingestion. agent_reported is capped at 4,000 serialized characters, machine_observed at 10,000. When present, the fields are stored on the request, included in the signed evidence packet, and summarized in the Activity entry for request_created (tool, trigger, and a clipped agent justification).

Policy context is not Microsoft-only

policy_context is a general evidence envelope for the reason a human was required. Use it with any system that evaluates risk or policy before the agent acts.

If you do not run a formal policy engine yet, you can still send a source such as custom_rules, finance_service, support_risk_classifier, internal_policy, opa, cedar, or workflow_guard. The important part is that future auditors can see which rule, version, and reason caused the human review.

custom-policy-context.json
{
  "risk_level": "high",
  "policy_trigger": "Vendor transfers above $10,000 require finance approval.",
  "policy_context": {
    "source": "custom_rules",
    "policy_name": "finance-transfer-controls",
    "rule_id": "vendor-transfer-over-10000",
    "rule_reason": "Vendor transfers above $10,000 require finance approval.",
    "policy_version": "git:8f42c1a",
    "enforcement": "require_approval"
  },
  "approval_comment_required": true
}

Decision context fields

Decision context fields are optional. Existing clients can ignore them, and low-risk audit-only actions should still use logAction instead of creating an approval request.

Use them when your runtime already decided a human must review the action. This makes the request easier to audit without moving policy logic into Contro1.

The same fields also fit governance evidence: risk_level maps to risk classification, policy_trigger explains why review was required, policy_context preserves the policy source and rule, approval_requirements captures expected reviewers, and correlation_id/case_id connects records to a broader case or AI use case inventory.

  • risk_level: caller-assessed risk: low, medium, high, or critical
  • policy_trigger: short human-readable policy reason, such as "Payments above $10,000 require finance approval"
  • policy_context: policy source, policy name, rule id, rule reason, version, and enforcement mode from an external policy engine
  • decision_comment_policy is configured per API key: optional allows one-click approve/reject, risk_based requires a note for rejection and high/critical decisions, and always requires a note for every approval decision
  • approval_comment_required: request-level tightening that snapshots the effective policy as always; a request cannot loosen the API-key policy
  • approval_requirements: expected approval count and roles for audit context
  • approval_policy: optional enforced quorum, role, separation-of-duties, and fail-closed behavior
  • decision_context.reviewer_mappings: optional evidence of requested external roles, mapped reviewers, fallback or manual routing targets, and mapping warnings
  • decision comment: required according to the snapshotted decision_comment_policy; it is distinct from the policy trigger, agent justification, and free-text human response

Control Map preview

Agents can call POST /api/centcom/v1/requests/control-map with the same approval policy shape to preview whether routing is currently satisfiable.

Control Map returns known departments, requested external role mappings, unmapped roles, on-shift capacity, fallback or manual routing targets, and warnings such as insufficient distinct approvers.

Use this to understand setup gaps, such as an unmapped CFO role or a two-person approval that cannot be satisfied with the current shift.

If a real request is sent with an unknown external role, Contro1 can keep the request pending while workspace admins choose who should receive it. Once the mapping is saved, future requests for that role route automatically.

A single member can hold multiple internal roles in the database, and the external role mapping popup can map multiple external roles to the same person. Separation-of-duties still checks for distinct approvers when the policy requires it.

control-map-request.json
{
  "type": "approval",
  "question": "Preview routing for invoice transfer",
  "context": "Do not create a request yet.",
  "approval_policy": {
    "mode": "threshold",
    "required_approvals": 2,
    "required_roles": ["finance", "cfo"],
    "separation_of_duties": true
  },
  "approval_requirements": {
    "required_approvals": 2,
    "must_include_roles": ["cfo"]
  }
}

Response fields

Create and get responses include both the operational state machine and protocol adapter fields so SDKs can consume one shape across runtimes.

routing_source explains where routing came from. api_key_default means the request used the default routing configured for the API key in Settings -> APIs & Webhooks unless the request body supplied an override.

  • state is the internal lifecycle state, such as queued, assigned, viewed, callback_pending, callback_delivered, or closed
  • status is the protocol-friendly outcome, such as pending, approved, denied, cancelled, or timed_out
  • request_type maps the request into the protocol model: approval, input, decision, or review
  • continuation_mode describes how the caller resumes: decision or instruction
  • structured_response contains the operator decision payload
  • risk_level, policy_trigger, policy_context, approval_comment_required, decision_comment_policy, decision_context, and allowed_decisions are returned with the request
  • response.decision_type is canonical: approve, reject, or respond; approved and value remain for backward compatibility
  • protocol_response is the canonical Contro1 Integration Protocol response, included for SDK and connector adapters

Cases and audit records

Use external_request_id for one external action and idempotency. Use correlation_id (shown in the dashboard as Case ID) for the broader business case. Use in_reply_to when a new request or audit record directly follows a prior Contro1 item.

Every request and audit record that shares a correlation_id appears together in one case timeline. In the UI, a single-item case shows the Case ID; multi-item cases show an Open case button.

Use audit records for actions that do not need a human before execution, but still need durable evidence in the same timeline.

Audit records and cases reference

Make the approval the gate, not a suggestion

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.

Full guardrail pattern with code: Webhooks

Frequently asked questions

What is the difference between approval and yes_no?

approval is better for higher-risk actions because it supports approve or reject semantics and a comment, while yes_no is better for simpler binary prompts.

Should every action become an approval request?

No. If the agent is already authorized to act, use logAction or POST /audit-records. Create a request only when a human must decide before the workflow continues.

Should I always use required_role?

Use it whenever the decision has ownership boundaries, such as HR, finance, security, or management approvals.

How do I stop the same request from being created twice?

Send a stable external_request_id. Retries that reuse the same value return the original request instead of creating a new one.

Can I cancel a request in flight?

Yes. Call DELETE /api/centcom/v1/requests/{id} - the operator view closes and your callback receives a cancelled status.

How do I get the agent's reason for the action into the request?

Make the justification a required parameter of the risky tool itself, for example transfer_money(to, amount, reason). The model fills it in at decision time and your gate forwards it with the action. Label it agent-reported in context, and never let it change routing or risk_level - a prompt-injected agent writes very persuasive reasons.

Related resources

API endpoint index

A compact map of the public Contro1 runtime API: request creation, Control Map preview, audit records, and thread timelines.