CitationBenchTalk to Sales
API reference

Workspaces API — Manage Multi-Tenant Agency Client Portfolios

REST API for agencies to create, list, audit, and run bulk operations across many client workspaces. Includes per-workspace usage, integration health checks, and bulk-action endpoints for cross-portfolio automation.

The workspace API is how agencies manage their portfolio: create new client workspaces, list them, audit health, and run any tool across many at once. For the conceptual model, see Workspaces.

Endpoints

MethodPathPurpose
GET/v1/workspacesList workspaces the key can access
GET/v1/workspaces/{id}Get one workspace
POST/v1/workspacesCreate a workspace
PATCH/v1/workspaces/{id}Update settings, brand profile, integrations
DELETE/v1/workspaces/{id}Archive (soft delete)
POST/v1/workspaces/bulk-actionRun any tool across many workspaces
GET/v1/workspaces/{id}/usageCredits used + quotas
GET/v1/workspaces/{id}/healthIntegration health

GET /v1/workspaces

curl -G "https://api.citationbench.com/v1/workspaces" \
  -H "Authorization: Bearer sk_live_agency_***" \
  --data-urlencode "include=usage,health,lastActiveAt" \
  --data-urlencode "limit=50"
ParamNotes
includecsv: usage, health, lastActiveAt, brand, integrations
tagFilter by workspace tag (e.g., industry:fintech)
activetrue / false
limit, cursorPagination

Response

{
  "data": [
    {
      "id": "ws_acme",
      "name": "Acme PM",
      "primaryDomain": "acme.com",
      "tags": ["industry:saas", "tier:premium"],
      "active": true,
      "createdAt": "2026-02-01T...",
      "lastActiveAt": "2026-05-24T08:14:32Z",
      "usage": {
        "creditsUsedThisCycle": 1247,
        "creditsRemaining": 1753,
        "cycleEndsAt": "2026-06-01T00:00:00Z"
      },
      "health": {
        "ahrefs": "healthy",
        "dataforseo": "healthy",
        "gsc": "warning",
        "apollo": "not_connected"
      }
    },
    { "id": "ws_beta", "...": "..." }
  ],
  "nextCursor": null,
  "total": 38
}

GET /v1/workspaces/{id}

curl https://api.citationbench.com/v1/workspaces/ws_acme \
  -H "Authorization: Bearer sk_live_agency_***"

Returns full workspace data including settings, brand profile, integration list, recent activity summary. See the Workspaces concept for the full shape.


POST /v1/workspaces

curl -X POST https://api.citationbench.com/v1/workspaces \
  -H "Authorization: Bearer sk_live_agency_***" \
  -d '{
    "name":          "Beta Project Management",
    "primaryDomain": "betapm.com",
    "tags":          ["industry:saas", "tier:standard"],
    "settings": {
      "primaryLocation":  "uk",
      "primaryLanguage":  "en",
      "defaultModel":     "claude-sonnet-4-6",
      "approvalPolicy": {
        "publish":         "require_approval",
        "outreach_send":   "require_approval"
      },
      "creditPool":       "agency"
    },
    "brand": {
      "shortDescription": "Project management for design and creative teams",
      "primaryKeywords":  ["project management for design teams"]
    }
  }'

Response: 201 Created with the new workspace's full record.


PATCH /v1/workspaces/{id}

curl -X PATCH https://api.citationbench.com/v1/workspaces/ws_beta \
  -d '{
    "settings": {
      "approvalPolicy": { "publish": "auto" }
    }
  }'

Partial updates. Any subset of name, tags, active, settings, brand accepted.


DELETE /v1/workspaces/{id}

Soft delete — sets active: false. Data retained for 90 days, then purged.

curl -X DELETE https://api.citationbench.com/v1/workspaces/ws_beta

Returns 204 No Content.


POST /v1/workspaces/bulk-action

The agency-defining endpoint. Run any tool across many workspaces with one call.

curl -X POST https://api.citationbench.com/v1/workspaces/bulk-action \
  -H "Authorization: Bearer sk_live_agency_***" \
  -d '{
    "action":     "agent.invoke",
    "workspaces": "all",
    "config": {
      "skill": "rank_monitor",
      "input": { "alertOn": { "drop": 5 } }
    }
  }'

Parameters

FieldTypeRequiredNotes
actionstringyesAny tool name (agent.invoke, research.keyword.research, indexing.gsc.submit, link_building.serp_outreach.create, etc.)
workspacesstring[] | "all"yesExplicit IDs or "all"
filterobjectnoWhen workspaces: "all", narrow by tag / active / etc.
configobjectyesBase config passed to the action; per-workspace variables ({{ workspace.X }}) resolve at runtime
concurrencynumbernoMax parallel children (default: 10)
schedulestringnoCron-style — recurring instead of one-shot ("weekly:mon:09:00", "daily:22:00", etc.)
tagsstring[]noTags on the parent invocation for later filtering

Response

HTTP/1.1 202 Accepted

{
  "parentInvocationId": "inv_01HVZ...",
  "agentId":            "agt_01HVZ...",
  "status":             "RUNNING",
  "workspacesFanout":   30,
  "estimatedCost":      { "credits": 450, "durationSeconds": 180 },
  "children": [
    { "workspaceId": "ws_acme",  "invocationId": "inv_***A", "status": "PENDING" },
    { "workspaceId": "ws_beta",  "invocationId": "inv_***B", "status": "PENDING" },
    { "workspaceId": "ws_gamma", "invocationId": "inv_***C", "status": "PENDING" }
    // 27 more
  ]
}

When complete, the parent invocation's result is an aggregate summary with per-workspace results plus a list of failures.

Workspace-variable substitution

Inside config, use {{ workspace.* }} placeholders that resolve per-child:

{
  "action": "link_building.serp_outreach.create",
  "workspaces": "all",
  "config": {
    "seed": { "keyword": "{{ workspace.brand.primaryKeywords[0] }}" },
    "outreach": { "ourContentUrl": "{{ workspace.brand.featuredContentUrl }}" }
  }
}

GET /v1/workspaces/{id}/usage

curl https://api.citationbench.com/v1/workspaces/ws_acme/usage
{
  "workspaceId": "ws_acme",
  "billingCycle": {
    "startDate": "2026-05-01T00:00:00Z",
    "endDate": "2026-06-01T00:00:00Z",
    "durationDays": 31
  },
  "credits": {
    "allocated": 3000,
    "used": 1247,
    "remaining": 1753,
    "rolloverEligible": 506
  },
  "breakdown": {
    "research": 412,
    "produce": 587,
    "indexing": 82,
    "link_building": 166
  },
  "quotas": {
    "gscIndexingDaily": { "limit": 10, "usedToday": 7 }
  }
}

GET /v1/workspaces/{id}/health

curl https://api.citationbench.com/v1/workspaces/ws_acme/health
{
  "status": "warning",
  "integrations": [
    { "name": "ahrefs", "status": "healthy", "lastCheckedAt": "..." },
    { "name": "dataforseo", "status": "healthy", "lastCheckedAt": "..." },
    {
      "name": "gsc",
      "status": "warning",
      "message": "Cookie expires in 3 days",
      "lastCheckedAt": "..."
    },
    { "name": "apollo", "status": "not_connected" },
    { "name": "instantly", "status": "healthy", "lastCheckedAt": "..." }
  ]
}

MCP

> List all my workspaces.

Claude calls workspaces.list.

> Run a rank check across every workspace, alert on drops of 5+ positions.

Claude calls workspaces.bulk_action.

> Create a new workspace for newclient.com.

Claude calls workspaces.create.


Errors

StatusCodeCause
400validation_errorMissing required fields
403agency_key_requiredEndpoint requires an agency master key
403workspace_forbiddenKey can't act on this workspace
404workspace_not_found
409workspace_existsSame primaryDomain already has a workspace under this agency
422no_matching_workspacesfilter matched zero workspaces

Cost

ActionCredits
All workspace CRUDfree
bulk-actionsum of per-workspace costs

Use cases (string things together)

A. Monday-morning portfolio report

curl -X POST .../v1/workspaces/bulk-action -d '{
  "action":     "distribute.track_rank",
  "workspaces": "all",
  "schedule":   "weekly:mon:09:00",
  "config":     { "alertOn": { "drop": 5 } }
}'

One scheduled job; one alert webhook; one client report.

B. Onboard 5 clients at once

for DOMAIN in client1.com client2.com client3.com client4.com client5.com; do
  WS=$(curl -sf -X POST .../v1/workspaces \
    -d "{\"name\":\"$DOMAIN\",\"primaryDomain\":\"$DOMAIN\"}" | jq -r '.id')
  curl -X POST .../v1/agent/invoke \
    -H "X-Workspace-Id: $WS" \
    -d "{\"skill\":\"bootstrap_brand\",\"input\":{\"domain\":\"$DOMAIN\"}}"
done

C. Audit which workspaces have unhealthy GSC

curl -G .../v1/workspaces --data-urlencode "include=health" \
  | jq '.data[] | select(.health.gsc != "healthy") | .id'

D. Cross-portfolio content gap

curl -X POST .../v1/workspaces/bulk-action -d '{
  "action":     "research.content_gap.find",
  "workspaces": "all",
  "config":     { "competitors": "{{ workspace.competitors.top_3 }}" }
}'

Surfaces gaps across every client; useful for finding agency-wide content themes.

On this page