---
title: Agent Integration
description: "Reference tables, endpoint maps, and plain-text instructions for AI agents (Claude Code, Cursor, Codex) to autonomously operate the Boring Marketing API."
---

> **For AI agents:** the complete documentation index is at [llms.txt](https://docs.boringmarketing.com/llms.txt). Append `.md` to any page URL for its markdown version.

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:

```bash
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.

<Tip>
  **Human running this instead of an agent?** You can complete the same Stripe checkout flow in a browser at [dashboard.boringmarketing.com](https://dashboard.boringmarketing.com). After checkout, your API key is visible in Settings.
</Tip>

## Documentation MCP server

These docs include a [documentation MCP server](https://modelcontextprotocol.io) that lets your agent search and read documentation interactively.

<Warning>
**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.
</Warning>

<Tabs>
  <Tab title="Claude Code">
    ```bash
    claude mcp add --transport http boring-marketing-docs https://docs.boringmarketing.com/_mcp
    ```
  </Tab>
  <Tab title="Cursor">
    Add to `.cursor/mcp.json` in your project:

    ```json
    {
      "mcpServers": {
        "boring-marketing-docs": {
          "url": "https://docs.boringmarketing.com/_mcp"
        }
      }
    }
    ```
  </Tab>
</Tabs>

The MCP server exposes `searchDocs` (keyword search) and `getPage` (fetch by slug). See the full [Documentation MCP](https://docs.boringmarketing.com/guides/mcp-server) 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:

````markdown
## 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} (201; adds `existing: true` if domain already registered) |
| 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

```json
{
  "id": "uuid",
  "family": "Topic Family Name",
  "play": "5-10 word play name",
  "summary": "Why this opportunity exists",
  "recommended_format": "comparison",
  "keywords": [{ "keyword": "keyword1", "google_vol": 18000 }],
  "total_volume": 45000,
  "rank": 1,
  "priority": 0.92,
  "immediacy": 0.85,
  "ceiling": 0.78,
  "leverage_score": 1.10,
  "contributing_signals": [
    { "evidence_id": "ev-1", "signal": "missing_topic_gap", "type": "gap_signal", "role": "primary" }
  ],
  "why_now": "...",
  "next_action": "...",
  "expected_impact": "..."
}
```

All three scores (`priority`, `immediacy`, `ceiling`) are 0-1. Higher is better. `recommended_format` is one of: `hub_and_spokes`, `single_article`, `programmatic`, `comparison`, `landing_page`.

### Queue item object

```json
{
  "id": "uuid",
  "kind": "opportunity",
  "source_id": "opp-uuid",
  "status": "pending",
  "rank": 1,
  "priority": 0.95,
  "item": { "play": "...", "summary": "...", "recommended_format": "...", "family": "..." }
}
```

`kind` is either `opportunity` (content to create) or `insight` (brand issue to fix). Pass the queue item's `id` (not `source_id`) as `queue_item_id` to `POST /brands/{id}/brief` to draft a brief.

### Run status object

```json
{
  "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

```json
{
  "error": "credits_exhausted",
  "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/usage` before starting a pipeline run to confirm sufficient calls remain
- Store `brand_id` and `run_id` values 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=X` on 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_id` to its `execution_id`
- Content production (brief + execute) requires Builder tier ($49/mo) or above
