Agent Integration
Reference tables, endpoint maps, and plain-text instructions for AI agents (Claude Code, Cursor, Codex) to autonomously operate the Boring Marketing API.
The API is built for AI agents. Every response is structured JSON. Every error includes machine-readable recovery steps. This page is the reference an agent needs to operate the full pipeline without human help.
Setup
Store the API key where your agent can access it:
export BM_API_KEY="bm_your_key_here"
For Claude Code, add it to your shell profile or project .env. For Cursor and Codex, set it in the environment config.
Human running this instead of an agent? You can complete the same Stripe checkout flow in a browser at dashboard.boringmarketing.com. After checkout, your API key is visible in Settings.
Documentation MCP server
These docs include a documentation MCP server that lets your agent search and read documentation interactively.
This MCP exposes documentation only. It does not call the Boring Marketing API. Use it to search these docs; to run pipelines and fetch data, your agent must call the REST API directly with an API key.
claude mcp add --transport http boring-marketing-docs https://docs.boringmarketing.com/_mcpThe MCP server exposes searchDocs (keyword search) and getPage (fetch by slug). See the full Documentation MCP reference.
Agent instructions (copy into CLAUDE.md or agent context)
The following block is designed to be dropped directly into a CLAUDE.md, .cursorrules, or any agent context file:
## Boring Marketing API
Base URL: https://api.boringmarketing.com
Auth: X-API-Key header using $BM_API_KEY environment variable
Content-Type: application/json for all POST/PATCH requests
### Getting an API key (Stripe-gated)
Accounts are created via Stripe checkout. There is no free tier — POST /auth/register returns 410 Gone.
Anonymous flow (agent has no existing key):
1. POST /auth/checkout with {email, name, tier}, NO X-API-Key header → returns {status: "email_sent", session_id, poll_url, sent_at}
2. Server emails the Stripe checkout URL to {email}. Tell the user to check their inbox and complete payment.
3. After payment, poll {poll_url} until status=="completed" → returns {status, dashboard_url}
4. The poll endpoint does NOT return an API key. Direct the user to sign into dashboard_url with a magic-link (sent to {email}) and copy their key from Settings → API Access.
Authenticated fast path (agent already has a valid key matching {email}):
1. POST /auth/checkout with {email, name, tier} AND X-API-Key header → returns {checkout_url, session_id, poll_url, flow}
2. Open checkout_url directly. Used for tier upgrades, reactivation, and billing portal access.
### Quick reference
| Task | Method | Endpoint | Body | Returns |
|------|--------|----------|------|---------|
| Start checkout (anonymous) | POST | /auth/checkout | {email, name, tier} | {status: "email_sent", session_id, poll_url, sent_at} |
| Start checkout (authenticated) | POST | /auth/checkout | {email, name, tier} + X-API-Key | {checkout_url, session_id, poll_url, flow} |
| Poll checkout | GET | /auth/checkout/{session_id}/status | — | {status, dashboard_url?} or {status, tier, calls_per_day, brand_limit} |
| Who am I | GET | /auth/me | — | current user |
| Check usage | GET | /auth/usage | — | {calls_today, calls_remaining} |
| Rotate key | POST | /auth/rotate-key | — | {api_key} (new) |
| List brands | GET | /brands | — | bare array of brands |
| Create brand | POST | /brands | {domain, name} | {id, domain, name, state} (201) |
| Get brand | GET | /brands/{brand_id} | — | full brand context (~25 fields) |
| Update brand | PATCH | /brands/{brand_id} | {name?, competitors?} | updated brand summary |
| Delete brand | DELETE | /brands/{brand_id} | — | {deleted: true, brand_id, domain} |
| Reset brand data | POST | /brands/{brand_id}/reset | — | {brand_id, cleared: {table: count}} |
| Discover competitors | POST | /brands/{brand_id}/discover-competitors | — | {run_id} (202) |
| Run full pipeline | POST | /brands/{brand_id}/analyze | — | {run_id} (202) |
| Discovery only | POST | /brands/{brand_id}/discover | — | {run_id} (202) |
| Brand enrichment | POST | /brands/{brand_id}/enrich/brand | — | {run_id} (202) |
| Technical enrichment | POST | /brands/{brand_id}/enrich/technical | {trigger: true} or {audit_data} | {run_id} (202) |
| LLM enrichment | POST | /brands/{brand_id}/enrich/llm | — | {run_id} (202) |
| Re-synthesize OE | POST | /brands/{brand_id}/synthesize | — | {run_id} (202) |
| Re-score queue | POST | /brands/{brand_id}/score | — | {run_id} (202) |
| List runs | GET | /brands/{brand_id}/runs | — | {brand_id, total, runs: [...]} |
| Get run status | GET | /brands/{brand_id}/runs/{run_id} | — | run object with meta.current_stage |
| Cancel stuck run | POST | /brands/{brand_id}/runs/{run_id}/cancel | — | {cancelled: true} |
| Get families | GET | /brands/{brand_id}/families | — | keyword universe by family |
| Get evidence | GET | /brands/{brand_id}/evidence | — | SERP + citation evidence |
| Get opportunities | GET | /brands/{brand_id}/opportunities | — | ranked plays by priority |
| Get insights | GET | /brands/{brand_id}/insights | — | brand findings |
| Get queue | GET | /brands/{brand_id}/queue | — | prioritized action items |
| Get queue item | GET | /brands/{brand_id}/queue/{queue_id} | — | single queue item detail |
| Get outcomes | GET | /brands/{brand_id}/outcomes | — | outcome events with milestone data |
| Check outcomes | POST | /brands/{brand_id}/outcomes/check | — | runs 7/30/90-day milestone checks |
| Generate brief | POST | /brands/{brand_id}/brief | {queue_item_id} | {run_id} (202) |
| List briefs | GET | /brands/{brand_id}/briefs | — | {brand_id, total, briefs} |
| Draft article | POST | /brands/{brand_id}/execute | {brief_id} | {run_id} (202) |
| List executions | GET | /brands/{brand_id}/executions | — | all executions |
| Get execution | GET | /brands/{brand_id}/executions/{execution_id} | — | execution with outputs field |
| Track rankings | GET | /track/rankings?brand_id=X | — | current Google positions |
| Track citations | GET | /track/citations?brand_id=X | — | LLM citation counts + impressions |
| Track AIO | GET | /track/aio?brand_id=X | — | AI Overview presence per hub keyword |
| Monthly report | GET | /track/report?brand_id=X | — | rankings + citations + competitors |
| Create snapshot | POST | /track/brands/{brand_id}/snapshot | — | tracking snapshot for diff |
| Get changes | GET | /track/brands/{brand_id}/changes?since=ISO | — | diff vs earlier snapshot |
| Report outcome | POST | /track/outcomes/report | {brand_id, recommendation_id, event_type, ...} | {outcome_event_id} |
### Path convention
All brand CRUD and pipeline endpoints use the plural /brands/ namespace.
A legacy singular /brand/* router exists for backward compatibility but
should not be used for new integrations.
### Async pattern
All pipeline endpoints return 202 with {run_id, status: "queued"}.
Poll GET /brands/{brand_id}/runs/{run_id} every 10-15 seconds.
Run status values: queued → running → done | failed.
Stage progress lives under meta.current_stage on the run object, NOT
at the top level. Top-level timestamps are started_at and finished_at.
Example running run:
{
"id": "run-uuid",
"brand_id": "brand-uuid",
"kind": "full",
"status": "running",
"error": null,
"started_at": "2026-04-07T10:00:00Z",
"finished_at": null,
"meta": {
"current_stage": "evidence_collection",
"stage_detail": "analyzing family 7 of 12",
"stage_started_at": "2026-04-07T10:06:42Z"
}
}
Pipeline stages emitted by /analyze in order:
1. brand_enrichment — crawling brand site + identity extraction
2. competitor_discovery — finding competitors via Tavily
3. family_discovery — building keyword universe
4. evidence_collection — SERPs per family (synthesis runs inside this stage)
5. leverage_scoring — cross-referencing brand gaps with opportunities
6. queue_building — assembling ranked action queue
Run kinds: full, brand_enrichment, technical_enrichment, llm_enrichment,
discovery, scoring, competitor_discovery, synthesize, brief, execution.
### Error handling
- 401: API key missing or invalid. Check X-API-Key header.
- 402: Tier limit exceeded. Response includes usage.limit and usage.used.
- 409: Pipeline already running. Check X-Running-Run-Id response header.
- 429: Rate limited. Respect Retry-After header.
All error responses include an "actions" array with machine-readable recovery:
{"error": "...", "message": "...", "actions": [{"rel": "checkout", "href": "/auth/checkout", "method": "POST"}]}
### Content production (requires Builder tier or above)
1. GET /brands/{brand_id}/queue — pick the top pending item
2. POST /brands/{brand_id}/brief with {"queue_item_id": "..."} — returns run_id, poll until done
3. GET /brands/{brand_id}/briefs — review the generated brief, extract brief_id
4. POST /brands/{brand_id}/execute with {"brief_id": "..."} — returns run_id, poll until done
5. GET /brands/{brand_id}/executions — find the execution_id for this run
6. GET /brands/{brand_id}/executions/{execution_id} — article content is in the "outputs" field
7. After publishing:
POST /track/outcomes/report with {"brand_id": "...", "recommendation_id": "...", "event_type": "completed"}
POST /track/brands/{brand_id}/snapshot — baseline rankings + citations for diffing
### Workflow: first-time brand analysis
1. POST /brands with {domain, name}
2. POST /brands/{brand_id}/analyze — triggers full pipeline
3. Poll /brands/{brand_id}/runs/{run_id} until status=done (15-30 minutes)
4. GET /brands/{brand_id}/opportunities — ranked content opportunities
5. GET /brands/{brand_id}/queue — action items sorted by leverage score
### Workflow: daily check
1. GET /brands/{brand_id}/opportunities — review current priorities
2. GET /track/rankings?brand_id=X — check ranking changes
3. GET /track/citations?brand_id=X — check LLM citation counts
4. GET /brands/{brand_id}/outcomes — review learning-loop milestones for published content
### Workflow: re-score after brand updates
1. PATCH /brands/{brand_id} with updated competitors
2. POST /brands/{brand_id}/score — re-runs leverage scoring
3. Poll until done, then GET /brands/{brand_id}/queue for updated priorities
Response shapes agents should expect
Opportunity object
{
"id": "uuid",
"family": "Topic Family Name",
"play": "5-10 word play name",
"summary": "Why this opportunity exists",
"keywords": ["keyword1", "keyword2"],
"priority": 0.92,
"immediacy": 0.85,
"ceiling": 0.78,
"total_volume": 45000,
"recommended_format": "comparison_post",
"contributing_signals": [
{ "type": "gap_signal", "evidence": "..." }
]
}
All three scores (priority, immediacy, ceiling) are 0-1. Higher is better. recommended_format is one of: single_article, comparison_post, pillar_guide, hub_and_spokes.
Queue item object
{
"id": "uuid",
"kind": "opportunity",
"status": "pending",
"rank": 1,
"priority": 0.95,
"item": { "play": "...", "summary": "...", "recommended_format": "..." }
}
kind is either opportunity (content to create) or insight (brand issue to fix). Pass the id to the brief endpoint for content items.
Run status object
{
"id": "uuid",
"brand_id": "brand-uuid",
"kind": "full",
"status": "running",
"error": null,
"started_at": "2026-04-07T10:00:00Z",
"finished_at": null,
"meta": {
"current_stage": "evidence_collection",
"stage_detail": "analyzing SERPs for family 7 of 12",
"stage_started_at": "2026-04-07T10:06:42Z"
}
}
Error object
{
"error": "tier_limit_exceeded",
"message": "Daily limit reached for your tier.",
"usage": { "limit": 500, "used": 500 },
"actions": [
{ "rel": "upgrade", "href": "/auth/billing-portal", "method": "POST" }
]
}
Agents should check the actions array and present the recovery option to the user.
Status codes quick reference
| Code | Meaning | Agent action |
|---|---|---|
200 | Success | Parse response JSON |
201 | Created (sync) | Parse created resource |
202 | Accepted (async) | Extract run_id, start polling |
401 | Auth failed | Prompt user to check/set $BM_API_KEY |
402 | Tier limit | Show usage, suggest upgrade via billing portal |
404 | Not found | Verify brand_id or resource ID |
409 | Duplicate run | Read X-Running-Run-Id header, poll that run instead |
429 | Rate limited | Wait Retry-After seconds, then retry |
Tips for reliable agent operation
- Always check
GET /auth/usagebefore starting a pipeline run to confirm sufficient calls remain - Store
brand_idandrun_idvalues from responses — you will need them for subsequent calls - The full pipeline (
/analyze) takes 15-30 minutes; set user expectations before triggering - Pin results to a specific run with
?run_id=Xon families, evidence, opportunities, and insights endpoints - Brief generation requires a
queue_item_id— always fetch the queue first - Execution IDs are distinct from run IDs — list executions to map a
brief_idto itsexecution_id - Content production (brief + execute) requires Builder tier ($49/mo) or above