Agent invoke API — universal entry point for running CitationBench skills
POST /v1/agent/invoke is the universal entry point for running the CitationBench agent. Pick a skill, pass its input, get an invocation handle to stream events, approve, cancel, or read results.
The universal entry point for running the CitationBench agent. Pick a skill from the registry, pass its input, get back an invocation handle. The handle lets you stream events, approve pauses, cancel, and read the final result.
CitationBench has one agent that loads skills on demand. bootstrap_brand, link_hunter, rank_monitor — these are skills, not separate agents. See Concept · Agent for the model.
Conceptual overview
An invocation is one durable run of one skill (which may chain into child invocations for sub-skills). It has:
- a unique
invocationId(CUID) - an
agentId(CUID) — the specific agent instance that ran - a status state machine:
PENDING→RUNNING→ (WAITING_APPROVAL|WAITING_INPUT|WAITING_CHILDREN) →SUCCEEDED|FAILED|CANCELLED - a graph of child invocations (when the agent composes sub-skills)
- a structured
resulton success, plusraw(agent narration) andfiles(paths the agent wrote) - a stream of events you can subscribe to
You can run an invocation FOREGROUND (synchronous, the response includes the result when ready) or BACKGROUND (the response is immediate; you poll or subscribe). Most agency workflows use BACKGROUND.
→ Concept: Agent (read first if you haven't)
Endpoints
| Method | Path | Purpose |
|---|---|---|
| POST | /v1/agent/invoke | Start an invocation |
| GET | /v1/agent/invocations/{id} | Get current state |
| GET | /v1/agent/invocations/{id}/events | SSE stream of events |
| GET | /v1/agent/invocations/{id}/tree | Full parent–child graph |
| POST | /v1/agent/invocations/{id}/cancel | Cancel a running invocation |
| POST | /v1/agent/invocations/{id}/continue | Multi-turn continuation (chat skills) |
| GET | /v1/agent/invocations | List recent invocations |
| GET | /v1/agent/skills | List available skills |
POST /v1/agent/invoke
Request
POST /v1/agent/invoke HTTP/1.1
Host: api.citationbench.com
Authorization: Bearer sk_live_***
X-Workspace-Id: ws_acme
Content-Type: application/json
Idempotency-Key: 7f3a1b18-7d8b-4f3e-9c4b-2c1a3e0a9b8f
{
"skill": "bootstrap_brand",
"input": {
"domain": "acme.com",
"depth": "thorough",
"options": {
"competitorCount": 8,
"keywordTarget": 1000
}
},
"mode": "BACKGROUND",
"approval": {
"required": true,
"scope": "publish_or_outreach"
},
"budget": {
"maxCredits": 200,
"maxLlmCalls": 80
},
"files": ["seed-docs/competitor-brief.pdf"],
"tags": ["onboarding", "client:acme"]
}Parameters
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
skill | string | yes | — | Skill slug (built-in like bootstrap_brand, or custom:<slug> for a workspace-scoped one). Loaded by the generic agent at the start of the invocation. |
input | object | yes | — | Validated against the skill's inputSchema (JSON Schema). Discover the schema via GET /v1/agent/skills/{slug}. |
mode | "FOREGROUND" | "BACKGROUND" | no | BACKGROUND | FOREGROUND blocks until done. Max 60 s; longer skills must use BACKGROUND. |
approval.required | boolean | no | workspace policy | If true, the agent pauses at outside-world steps |
approval.scope | enum | no | all_writes | publish_or_outreach, all_writes, every_step |
budget.maxCredits | number | no | skill default | Hard cap; invocation fails if exceeded |
budget.maxLlmCalls | number | no | skill default | Same — caps the LLM-call loop |
files | string[] | no | — | File paths (see Agent · files). Made available to the agent as readable inputs. |
tags | string[] | no | [] | Free-text tags for filtering invocations later |
parentInvocationId | string | no | — | Sets this invocation as a child of another |
sessionId | string | no | — | For chat skills: continues an existing session |
Response — BACKGROUND mode
HTTP/1.1 202 Accepted
Content-Type: application/json
X-Request-Id: req_01HVZ...
{
"invocationId": "inv_01HVZRJZ3T8M7E0B0V0Z6KQ3A4",
"sessionId": "sess_01HVZRJZ3T8M7E0B0V0Z6KQ3A5",
"agentId": "agt_01HVZRJZ3T8M7E0B0V0Z6KQ3A6",
"skill": "bootstrap_brand",
"status": "PENDING",
"mode": "BACKGROUND",
"workspace": "ws_acme",
"estimatedCost": {
"credits": 152,
"durationSeconds": 1200
},
"createdAt": "2026-05-24T08:00:00Z",
"links": {
"self": "https://api.citationbench.com/v1/agent/invocations/inv_01HVZ...",
"events": "https://api.citationbench.com/v1/agent/invocations/inv_01HVZ.../events",
"tree": "https://api.citationbench.com/v1/agent/invocations/inv_01HVZ.../tree",
"cancel": "https://api.citationbench.com/v1/agent/invocations/inv_01HVZ.../cancel",
"approvals": "https://api.citationbench.com/v1/agent/approvals?invocationId=inv_01HVZ..."
}
}Response — FOREGROUND mode
When the agent finishes within the 60 s limit, the response is the same shape as the BACKGROUND mode + a populated result, raw, files, and agentId.
{
"invocationId": "inv_01HVZ...",
"agentId": "agt_01HVZ...",
"skill": "keyword_manager",
"skillsUsed": ["keyword_manager", "research.keyword.list"],
"status": "SUCCEEDED",
"creditsUsed": 12,
"llmCallsUsed": 4,
"durationMs": 18420,
"result": {
"summary": "Found 12 PROBLEM_SOLUTION keywords missing landing pages.",
"keywords": [
{
"id": "kw_***",
"keyword": "track team capacity in real time",
"kd": 22,
"volume": 480
},
{
"id": "kw_***",
"keyword": "engineering project management for distributed teams",
"kd": 31,
"volume": 320
}
]
},
"raw": "I started by listing every PROBLEM_SOLUTION keyword in the pricing pillar that didn't yet have a linked blog post or landing page. Of the 47 candidates, 12 had volume above 300 and KD under 35, which I shortlisted ...",
"files": ["agent-workspace/shortlist.csv", "agent-workspace/ranking-notes.md"]
}The universal response envelope
| Field | Always present | Notes |
|---|---|---|
invocationId | yes | inv_*** (CUID) |
agentId | yes | agt_*** (CUID) — the specific agent instance that ran this invocation |
skill | yes | The skill that was invoked |
skillsUsed | yes | Every skill the agent loaded during the run — primary + any sub-skills chained in |
status | yes | terminal: SUCCEEDED, FAILED, CANCELLED |
result | on SUCCEEDED | Structured output, shape defined by the skill's outputSchema |
raw | on SUCCEEDED or FAILED | The agent's raw text — its thinking, its narration, what it was about to do next. Higher fidelity than result. |
files | always (possibly empty) | Array of file paths the agent wrote during the run. Fetch any of them with the Agent · files API. |
error | on FAILED | Structured error |
The raw and files fields let you reconstruct the agent's full reasoning when the structured result isn't enough. Treat result as the typed contract and raw+files as the audit trail.
If the agent exceeds the 60 s budget, the response is 202 Accepted (same as BACKGROUND), and you poll or subscribe.
GET /v1/agent/invocations/{id}
curl -H "Authorization: Bearer sk_live_***" \
"https://api.citationbench.com/v1/agent/invocations/inv_01HVZ..."Response
{
"invocationId": "inv_01HVZ...",
"agentId": "agt_01HVZ...",
"skill": "bootstrap_brand",
"skillsUsed": [
"bootstrap_brand",
"research.icp",
"research.keyword",
"research.competitor"
],
"status": "WAITING_APPROVAL",
"mode": "BACKGROUND",
"workspace": "ws_acme",
"depth": 0,
"createdAt": "2026-05-24T08:00:00Z",
"updatedAt": "2026-05-24T08:14:32Z",
"startedAt": "2026-05-24T08:00:02Z",
"creditsUsed": 87,
"llmCallsUsed": 23,
"stepsCompleted": ["crawl", "icp", "keywords", "competitors", "discussions"],
"currentStep": {
"name": "content_plan_publish",
"status": "WAITING_APPROVAL",
"approvalId": "appr_01HVZ...",
"approvalUrl": "https://api.citationbench.com/v1/agent/approvals/appr_01HVZ..."
},
"children": [
{
"invocationId": "inv_01HVZ...A",
"skill": "research.icp",
"status": "SUCCEEDED"
},
{
"invocationId": "inv_01HVZ...B",
"skill": "research.keyword",
"status": "SUCCEEDED"
},
{
"invocationId": "inv_01HVZ...C",
"skill": "research.competitor",
"status": "SUCCEEDED"
}
],
"result": null,
"files": [
"agent-workspace/crawl-summary.md",
"agent-workspace/icp-segments.json",
"agent-workspace/keyword-shortlist.csv",
"agent-workspace/competitor-matrix.csv",
"agent-workspace/reddit-pain-points.md"
]
}When the invocation reaches SUCCEEDED, result is populated with the structured output (its shape is defined by the skill's outputSchema).
GET /v1/agent/invocations/{id}/events (SSE)
A server-sent event stream. Useful for live progress (Claude Code, dashboards).
event: status.changed
data: { "from": "PENDING", "to": "RUNNING", "at": "2026-05-24T08:00:02Z" }
event: step.started
data: { "step": "crawl", "tool": "produce.crawl", "input": { "domain": "acme.com" } }
event: step.completed
data: { "step": "crawl", "durationMs": 6200, "result": { "pagesCrawled": 14 } }
event: step.started
data: { "step": "icp", "tool": "research.icp" }
event: step.awaiting_approval
data: { "step": "content_plan_publish", "approvalId": "appr_01HVZ...", "preview": { "weekCount": 12, "totalPosts": 48 } }
# ... (more events until status.changed → SUCCEEDED)
event: status.changed
data: { "from": "RUNNING", "to": "SUCCEEDED", "creditsUsed": 147 }Event types: status.changed, step.started, step.completed, step.failed, step.awaiting_approval, step.approved, step.rejected, child.spawned, child.completed, llm.call, tool.call, cost.update.
POST /v1/agent/invocations/{id}/cancel
curl -X POST -H "Authorization: Bearer sk_live_***" \
"https://api.citationbench.com/v1/agent/invocations/inv_01HVZ.../cancel" \
-d '{ "reason": "Wrong workspace" }'Response: 200 OK with the invocation in status: "CANCELLED". Any in-flight child invocations are also cancelled. Unspent reserved credits are refunded.
POST /v1/agent/invocations/{id}/continue
For multi-turn / chat skills (keyword_manager, link_swap_evaluator, custom conversational skills). Appends a message to the same session and resumes the agent.
curl -X POST -H "Authorization: Bearer sk_live_***" \
"https://api.citationbench.com/v1/agent/invocations/inv_01HVZ.../continue" \
-H "Content-Type: application/json" \
-d '{
"input": { "message": "Now drop everything with KD greater than 40." }
}'Response: a new invocation ID (each turn is its own invocation), with parentInvocationId set to the prior one and sessionId shared.
GET /v1/agent/skills
List all available skills and their schemas.
curl -H "Authorization: Bearer sk_live_***" \
"https://api.citationbench.com/v1/agent/skills"Response
{
"skills": [
{
"slug": "bootstrap_brand",
"name": "Bootstrap a brand",
"description": "URL → full SEO+GEO operating plan in 20 min",
"category": "research",
"inputSchema": {
"type": "object",
"required": ["domain"],
"properties": {
"domain": {
"type": "string",
"description": "The brand's primary domain"
},
"depth": { "enum": ["fast", "thorough"], "default": "thorough" },
"options": { "type": "object", "additionalProperties": true }
}
},
"outputSchema": {
"type": "object",
"properties": {
"workspaceId": { "type": "string" },
"icp": { "type": "object" },
"keywordUniverse": { "type": "object" },
"competitors": { "type": "array" },
"painPoints": { "type": "array" },
"contentPlan": { "type": "object" },
"landingPageBriefs": { "type": "array" }
}
},
"defaultBudget": {
"credits": 152,
"llmCalls": 80,
"durationSeconds": 1200
},
"supportsApproval": true,
"supportsFiles": true
},
{ "slug": "rank_monitor", "...": "..." },
{ "slug": "link_hunter", "...": "..." },
{ "slug": "citation_hunter", "...": "..." },
{ "slug": "content_factory", "...": "..." },
{ "slug": "refresh_stale", "...": "..." },
{ "slug": "keyword_manager", "...": "..." },
{ "slug": "link_swap_evaluator", "...": "..." }
],
"customSkills": [
{
"slug": "custom:weekly-audit",
"createdAt": "2026-05-12T...",
"...": "..."
}
]
}MCP
Same surface, conversational.
> Use the bootstrap_brand skill to research acme.com. Pause before publishing anything.Claude resolves the skill slug, fetches its schema, and calls agent.invoke with the right input shape. Progress streams as MCP notifications.
> What invocations are still waiting on me?Claude calls agent.approval.list and renders the queue.
Errors
| Status | Code | Cause |
|---|---|---|
| 400 | validation_error | input failed the skill's schema; check the response details for the field |
| 400 | unknown_skill | skill is not in the registry or visible to your workspace |
| 401 | unauthorized | Missing or invalid API key (or you're hitting an endpoint that doesn't allow demo mode) |
| 402 | insufficient_credits | Reserved cost exceeds remaining credits |
| 403 | workspace_forbidden | Key cannot act on X-Workspace-Id |
| 409 | idempotency_replay | Same Idempotency-Key was used recently; returns the prior invocation |
| 422 | budget_too_low | budget.maxCredits below the skill's minimum |
| 429 | rate_limited | Per-key rate exceeded |
| 503 | skill_unavailable | Skill depends on an external service that's down (e.g., DataForSEO) |
Cost
Costs vary by skill. Built-in defaults:
| Skill | Approx credits | Approx duration |
|---|---|---|
bootstrap_brand (thorough) | 152 | 20 min |
bootstrap_brand (fast) | 78 | 10 min |
rank_monitor (per workspace per run) | 15 | 90 s |
link_hunter (per cycle) | 50 | 5 min |
citation_hunter (per cycle, 20 prompts × 4 LLMs) | 160 | 8 min |
content_factory (per article) | 50 | 4 min |
refresh_stale (per article) | 30 | 3 min |
keyword_manager (per turn) | 2 | 5 s |
Use ?dryRun=true on the invoke call to get a precise estimate without running.
Use cases (string things together)
A. Bootstrap an agency client, then immediately publish 6 landing pages
# 1. Bootstrap
INV=$(curl -sf -X POST .../v1/agent/invoke -d '{
"skill": "bootstrap_brand",
"input": { "domain": "acme.com" }
}' | jq -r '.invocationId')
# 2. Wait
until [[ "$(curl -sf .../v1/agent/invocations/$INV | jq -r '.status')" =~ ^(SUCCEEDED|FAILED)$ ]]; do sleep 30; done
# 3. Spawn one content_factory invocation per landing-page brief
curl -sf .../v1/agent/invocations/$INV \
| jq -r '.result.landingPageBriefs[] | @json' \
| while read BRIEF; do
curl -X POST .../v1/agent/invoke -d "{
\"skill\": \"content_factory\",
\"input\": { \"briefId\": $(echo $BRIEF | jq -r '.id') },
\"mode\": \"BACKGROUND\"
}"
doneB. One skill supervises another
A higher-level skill invokes a child skill and waits on its result.
const supervisor = await cb.agent.invoke({
skill: "custom:editorial-supervisor",
input: {
// The supervisor's prompt instructs the agent to call content_factory
// and then evaluate the output via produce.evaluate.
keywordId: "kw_***",
},
});
// Supervisor's tree shows the spawned content_factory child invocation:
const tree = await cb.agent.invocations.tree(supervisor.invocationId);C. Pause-and-edit pattern for client review
const inv = await cb.agent.invoke({
skill: "content_factory",
input: { keywordIds: [...] },
approval: { required: true, scope: "every_step" },
});
// Subscribe to events
const events = cb.agent.invocations.events(inv.invocationId);
for await (const ev of events) {
if (ev.type === "step.awaiting_approval") {
const decision = await ourPortal.askClient(ev.preview);
if (decision.approve) {
await cb.agent.approval.approve({ approvalId: ev.approvalId, edits: decision.edits });
} else {
await cb.agent.approval.reject({ approvalId: ev.approvalId, note: decision.note });
}
}
}D. Run the same skill across every client workspace
curl -X POST .../v1/workspaces/bulk-action \
-H "Authorization: Bearer sk_live_agency_***" \
-d '{
"action": "agent.invoke",
"workspaces": "all",
"config": {
"skill": "rank_monitor",
"input": { "scope": { "intent": ["PURCHASE"] }, "alertOn": { "drop": 5 } }
}
}'Returns one parent invocation with one child per workspace.
Related
- Concept: Agent
- API: Agent · files
- API: Agent · approval
- API: Inventory
- Playbook: Keyword research for a brand in 20 minutes
- Playbook: Build an SEO agent in Claude Code
Errors
CitationBench uses standard HTTP status codes plus a single error envelope with stable `code`, `message`, `details`, and `requestId` across REST and MCP. Branch on `code` in your client.
Files
Upload PDFs, CSVs, transcripts, and briefs as agent inputs, and read back artifacts the agent wrote during a run. Files are addressed by path, scoped per workspace.