---
title: API Overview
description: "Base URL, authentication, request format, error handling, rate limits, and async patterns for 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.

<Note>
**Reading these docs.** This reference is written to be read by both humans and AI agents. Most steps are identical for either audience. When the steps genuinely diverge — usually around signup, API key retrieval, billing portal, or account recovery — you'll see a callout whose first words are **Path split.** describing what each audience does. Agents consuming `/llms.txt` or `.md` page variants can grep for `Path split.` to find every audience-dependent handoff.
</Note>

## Base URL

All requests go to:

```
https://api.boringmarketing.com
```

## Authentication

All authenticated requests require the `X-API-Key` header:

```bash
X-API-Key: bm_your_api_key_here
```

You receive your API key by completing Stripe checkout via `POST /auth/checkout` — see the [Quickstart](https://docs.boringmarketing.com/quickstart) or the [Authentication guide](https://docs.boringmarketing.com/guides/authentication) for the full flow. Store it securely — it is your sole credential.

## Request format

- **Content-Type:** `application/json` for all POST/PATCH requests
- **Query parameters:** used for filtering on GET endpoints (`?brand_id=`, `?run_id=`, `?status=`)
- **Path parameters:** resource IDs in the URL path (`/brands/{brand_id}/opportunities`)

## Response format

All responses are JSON. Successful responses return the resource directly. Some list endpoints wrap results with `{brand_id, total, items}`; others return a bare array. Each endpoint reference page documents its exact shape — do not assume a uniform envelope.

Example wrapped response:

```json
{
  "brand_id": "uuid",
  "total": 42,
  "opportunities": [...]
}
```

Example bare array response (e.g. `GET /brands`):

```json
[
  { "id": "uuid-1", "domain": "...", "name": "...", "created_at": "...", "is_managed": false },
  { "id": "uuid-2", "domain": "...", "name": "...", "created_at": "...", "is_managed": false }
]
```

## Response headers

Every response includes:

| Header | Description |
|--------|------------|
| `X-Process-Time` | Request execution time in seconds |
| `X-Request-ID` | Unique 8-character request identifier |
| `X-Skill-Latest` | Latest skill version (authenticated only) |
| `X-Skill-Update-URL` | Skill update URL (authenticated only) |

## Async endpoints (202 pattern)

Pipeline endpoints return **202 Accepted** immediately. The work runs in the background.

```json
// 202 Accepted
{
  "run_id": "uuid",
  "status": "queued"
}
```

Poll `GET /brands/{id}/runs/{run_id}` for progress. See [Polling Runs](https://docs.boringmarketing.com/guides/polling-runs) for details.

**Endpoints that return 202:**
- `POST /brands/{id}/analyze`
- `POST /brands/{id}/enrich/brand`
- `POST /brands/{id}/enrich/technical`
- `POST /brands/{id}/enrich/llm`
- `POST /brands/{id}/discover`
- `POST /brands/{id}/discover-competitors`
- `POST /brands/{id}/synthesize`
- `POST /brands/{id}/score`
- `POST /brands/{id}/brief`
- `POST /brands/{id}/execute`

## Error responses

<Snippet file="error-codes.mdx" />

## Rate limits

<Snippet file="tier-table.mdx" />

Exceeding the daily limit returns `402 credits_exhausted`. Per-IP and per-email rate limits return `429 rate_limit_exceeded` with a `Retry-After` header and a `retry_after` body field.

## Path conventions

All brand CRUD and pipeline endpoints use the **plural** `/brands/{id}/...` namespace:

- `POST /brands` — create a brand
- `GET /brands/{id}` — read a brand
- `POST /brands/{id}/analyze` — run the full pipeline
- `GET /brands/{id}/opportunities` — read scored opportunities

<Note>
A legacy singular `/brand/*` router exists for backward compatibility with older integrations but is not recommended for new work. Always prefer the plural `/brands/*` endpoints.
</Note>
