API Overview
Base URL, authentication, request format, error handling, rate limits, and async patterns for the Boring Marketing API.
Base URL
All requests go to:
https://api.boringmarketing.com
Authentication
All authenticated requests require the X-API-Key header:
X-API-Key: bm_your_api_key_here
You receive your API key by completing Stripe checkout via POST /auth/checkout — see the Quickstart or the Authentication guide for the full flow. Store it securely — it is your sole credential.
Request format
- Content-Type:
application/jsonfor 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:
{
"brand_id": "uuid",
"total": 42,
"opportunities": [...]
}
Example bare array response (e.g. GET /brands):
[
{ "id": "uuid-1", "domain": "...", "name": "...", "state": "new", "created_at": "..." },
{ "id": "uuid-2", "domain": "...", "name": "...", "state": "new", "created_at": "..." }
]
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.
// 202 Accepted
{
"run_id": "uuid",
"status": "queued"
}
Poll GET /brands/{id}/runs/{run_id} for progress. See Polling Runs for details.
Endpoints that return 202:
POST /brands/{id}/analyzePOST /brands/{id}/enrich/brandPOST /brands/{id}/enrich/technicalPOST /brands/{id}/enrich/llmPOST /brands/{id}/discoverPOST /brands/{id}/discover-competitorsPOST /brands/{id}/synthesizePOST /brands/{id}/scorePOST /brands/{id}/briefPOST /brands/{id}/execute
Error responses
| Status | Error Code | Meaning | What to Do |
|---|---|---|---|
401 | authentication_required | Missing or invalid X-API-Key header | Check your API key is set correctly |
402 | tier_limit_exceeded | Daily call limit reached for your tier | Upgrade via POST /auth/checkout |
404 | — | Resource not found | Verify the brand_id or resource ID |
409 | — | Pipeline run already in progress | Poll the existing run instead (check X-Running-Run-Id header) |
422 | — | Validation error | Check request body against the docs |
429 | — | Rate limited | Wait and retry after Retry-After seconds |
All error responses follow this shape:
{
"error": "error_code",
"message": "Human-readable explanation",
"actions": [
{
"rel": "checkout",
"href": "/auth/checkout",
"method": "POST",
"description": "Start Stripe checkout to create an account"
}
]
}
The actions array provides machine-readable next steps — useful for AI agents to self-recover from errors.
Rate limits
| Tier | Daily calls | Brands |
|---|---|---|
| Builder | 500 | 1 |
| Pro | 5,000 | 3 |
| Agency | 50,000 | 10 |
Exceeding the daily limit returns 402. Rate-limited requests return 429 with a Retry-After header.
Path conventions
All brand CRUD and pipeline endpoints use the plural /brands/{id}/... namespace:
POST /brands— create a brandGET /brands/{id}— read a brandPOST /brands/{id}/analyze— run the full pipelineGET /brands/{id}/opportunities— read scored opportunities
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.