New at ContentCon: meet Contentstack Canoe. See what AI says about your brand.
Growth and Enterprise

API and webhooks

Pull results into your scripts, export PDFs and re-run a monitor from CI, with the same key your agents use.

Which one to use

MCP server, REST API and webhook trigger compared
SurfaceBest forAuth
MCP serverAgents in any MCP clientAPI key, Read & write
REST APIScripts, dashboards and PDF exportsAPI key, Read only or Read & write
Webhook triggerStarting an on-demand monitor from CI or a schedulerThe brand's webhook passkey
About the REST API

There's no versioned reference or OpenAPI spec yet, and fields may be added. For agents, MCP is steadier.

Authentication

Create a key in Settings, API keys and send it as Authorization: Bearer sk_... or x-api-key: sk_.... Try it:

Terminal
curl https://canoe.contentstack.com/api/projects \
  --header "Authorization: Bearer $CONTENTSTACK_CANOE_API_KEY"
Base URL
https://canoe.contentstack.com/api
Header
Bearer is case sensitive, followed by the key once.
Read only
GET only. Fine for dashboards and reports.
Read & write
Anything the key's creator can do, including runs and brand profile edits.
Format
JSON. Most fields are snake_case, but some older ones are camelCase, like urlId, jobId and generatedAt.

Identity, limits and revoking are in How keys work.

Endpoints

A brand is a project. Each monitor has one id: results endpoints call it test_id, and monitor and run endpoints call it url_id (urlId in request bodies).

  • BrandsList brands, 10 per page by default (use skip and limit), or read one. Each brand's urls list carries its monitors' url_id.
    GET /api/projectsGET /api/projects/:id
  • Brand profileNames, competitors, products and personas
    GET /api/projects/:projectId/brand-profile
  • Brand profile editsRead & writeAdd, change or remove competitors, products and personas. Products and icps use the same paths as competitors.
    POST .../brand-profile/competitorsPATCH DELETE .../brand-profile/competitors/:id
  • MonitorsOne monitor's settings, questions and status
    GET /api/test/url/:url_id
  • RunsRead & writeStart a run now. Spends prompts. Returns a jobId.
    POST /api/test/job/force-run { "urlId": "..." }
  • HistoryRuns and trends, newest first, with each run's PDF export state. history-runs pages start at 0, and limit is 1 to 50.
    GET /api/ai-section/test/:test_id/historyGET .../history-summaryGET .../history-runs?page=0&limit=20
  • ReportsThe full result of one run
    GET /api/ai-section/report/:reportId
  • CitationsSources by owner and domain, or by page. Filter with report_id, stage, from and to. stage is Awareness, Consideration or Decision, exact case.
    GET /api/gso-citations/summary?test_id=...GET /api/gso-citations/by-url?test_id=...
  • PDF reportsGenerate needs Read & writeGenerate a run's PDF, check it and download it
    POST /api/report/:id/pdf/generateGET /api/report/:id/pdf/statusGET /api/report/:id/pdf

Example: export a PDF report

Take the report id from the history endpoint.

Terminal
REPORT_ID="your_report_id"
AUTH="Authorization: Bearer $CONTENTSTACK_CANOE_API_KEY"

BASE="https://canoe.contentstack.com/api/report/$REPORT_ID/pdf"

# 1. Ask for the PDF
curl --request POST "$BASE/generate" --header "$AUTH"

# 2. Check until it's ready
curl "$BASE/status" --header "$AUTH"

# 3. Download it
curl --output report.pdf "$BASE" --header "$AUTH"

Webhook trigger

Start an on-demand monitor from a deploy or a nightly job. It uses the brand's passkey, not your API key.

Get the trigger URL

  1. Set the monitor's schedule to On Demand. The Web hook option only appears on On Demand monitors.

  2. Open the monitor's menu (the three dots) and choose Web hook.

  3. Select Copy API Trigger URL, then copy the Secret Passkey (it starts with whsec_ and covers all the brand's on-demand monitors). Entry Key shows the header name to send it in. Store all three as CI secrets.

Call it

Send a GET with the passkey in the header that Entry Key names, spelled exactly as shown.

Terminal
curl --request GET \
      --url "https://canoe.contentstack.com/api/test/job/webhook-run/<webhook_run_id>" \
      --header "$CONTENTSTACK_CANOE_WEBHOOK_HEADER: $CONTENTSTACK_CANOE_WEBHOOK_KEY"
200
Queued. The reply is the monitor's record. Results in about two minutes.
401
The passkey header is missing or wrong.
403
Not enough prompts left for this run.
Skipped
"skipped": true with a reason of missing or deleted means the monitor is gone.
GET only

Contentstack CMS webhooks send POST, so they can't call it directly. Use a relay, a CI step or trigger_geo_test_run over MCP.

Rate limits

Monthly
10,000 API and MCP requests on Growth, shared by the workspace. Every call made with a key counts, including calls that return an error. Over MCP, only tool calls count. When they run out, calls get a 429 until the reset. Custom on Enterprise (coming soon).
Bursts
Limited per workspace. On a 429 Too many requests, back off and retry.
Webhook
Limited per IP address. Prompt limits apply.
Prompts
Reads are free. A run spends one prompt per question per engine. See Plans and limits.

Errors

401Key missing or not accepted

No key (No token provided), a revoked or mistyped key (Invalid API key) or a header that isn't Bearer plus one sk_ key, like Bearer twice (Invalid token).

What to do: check the header and the key.

403Read only key

A Read only key tried to change something.

What to do: use a Read & write key for POST, PATCH and DELETE.

403Workspace on Free

The key's workspace is on Free.

What to do: Upgrade to Growth.

403Out of prompts

Not enough prompts left to start a run.

What to do: wait for the monthly reset, or add a prompt pack on Growth.

404Id not in this workspace

The id isn't in this key's workspace. Some routes answer 404; others return {}, an empty runs list or zero counts.

What to do: list brands and monitors again and use those ids.

409Run already in progress

A run for this monitor is already in progress.

What to do: wait for it to finish.

429Too many requests

Too many requests in a short time.

What to do: back off and retry.

429Monthly requests used

The month's API and MCP requests are used. The reply has Retry-After and the reset date.

What to do: wait until the reset. Retrying sooner won't help.

Not available yet

  • Outbound webhooksComing soonNothing calls you back when a run ends. Today, poll the history endpoint or get_geo_latest_report.
  • Native alertsComing soonToday, run the Visibility alerts skill on your own schedule.

No SDK yet. Run reports export as PDF.