Authentication
API key authentication, tier system, rate limits, key rotation, and creating an account via Stripe checkout.
The API uses a single API key for authentication. No sessions, no OAuth, no passwords. One key, one header.
API key format
All keys start with bm_ followed by a hex string:
bm_54249af28748e0e24b4d2782122e2de9335627ef0f800747
Pass it in the X-API-Key header on every request:
curl -H "X-API-Key: $BM_API_KEY" \
https://api.boringmarketing.com/auth/me
Store your key in an environment variable. Never hardcode it in source files.
export BM_API_KEY="bm_your_key_here"Getting an API key
Accounts are created via Stripe checkout. You choose a tier, pay, then retrieve your key from the dashboard. There is no free tier and no self-serve /auth/register endpoint.
curl -s -X POST \
https://api.boringmarketing.com/auth/checkout \
-H "Content-Type: application/json" \
-d '{
"email": "you@example.com",
"name": "Your Name",
"tier": "solo"
}'
Response:
{
"status": "email_sent",
"session_id": "bmc_k7f3...",
"poll_url": "https://api.boringmarketing.com/auth/checkout/bmc_k7f3.../status",
"sent_at": "2026-04-08T18:42:11.123Z"
}
Path split. The next steps diverge based on who's reading.
If you're a human: check your email for the Stripe link, click it, complete payment. Then poll poll_url until it returns {status: "completed", dashboard_url}, open that URL, sign in with a magic-link, and copy your key from Settings → API Access.
If you're an agent running unattended: surface session_id and poll_url to your operator and stop. Polling will return completed after payment, but the response never contains an API key — the operator must sign into the dashboard and paste the key back before you can continue. To skip this handoff when you already hold a key (e.g. tier upgrade), use the authenticated fast path on POST /auth/checkout.
Check your inbox. The server emailed you a Stripe checkout link — click it, complete payment, then poll poll_url until status is completed:
{
"status": "completed",
"dashboard_url": "https://dashboard.boringmarketing.com"
}
The poll endpoint does not return your API key. Open dashboard_url in a browser, request a magic-link with your email, and copy your key from Settings → API Access. Then export it:
export BM_API_KEY="bm_your_key_here"
Already have a key but need to upgrade? The same POST /auth/checkout endpoint accepts an X-API-Key header for an authenticated fast path. In that mode, the response includes checkout_url inline (no email) and opens the Stripe billing portal for active users. See the checkout endpoint reference for details.
Tiers
| Builder | Pro | Agency | |
|---|---|---|---|
| Price | $49/mo | $149/mo | $399/mo |
| Calls/day | 500 | 5,000 | 50,000 |
| Brands | 1 | 3 | 10 |
All paid tiers include the full pipeline, brief generation, and content drafting — the only differences are daily call volume and concurrent brands. Start with Builder to run a full pipeline on one brand; upgrade to Pro or Agency when you need more brands or higher daily call volume.
Rate limits
Rate limits protect infrastructure. They are separate from tier call limits.
| Endpoint | Limit |
|---|---|
POST /auth/checkout (anonymous) | 3 per hour per destination email + 5 per hour per IP |
POST /auth/checkout (authenticated) | 5 per hour per IP |
POST /auth/recover | 3 per hour per IP |
| All authenticated endpoints | Tier-based daily call limit |
When rate limited, the response body includes a retry_after field (seconds) and the response carries a Retry-After HTTP header with the same value:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json
{
"error": "rate_limit_exceeded",
"message": "Too many checkout requests. Try again later.",
"retry_after": 60
}
Error handling
| Status | Error Code | Meaning | What to Do |
|---|---|---|---|
401 | authentication_required | Missing X-API-Key header | Set the header on your request |
401 | invalid_api_key | Header present but key is not recognized | Verify the key or rotate via POST /auth/rotate-key |
402 | credits_exhausted | Daily call limit reached for your tier | Upgrade via POST /auth/billing-portal |
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 |
Error responses come in two flat shapes — both at the top level of the JSON body, with no detail wrapper:
// Structured (most authenticated endpoints — auth gating, tier limits, rate limits)
{
"error": "error_code",
"message": "Human-readable explanation",
"actions": [
{
"rel": "checkout",
"href": "/auth/checkout",
"method": "POST",
"description": "Start Stripe checkout to create an account"
}
]
}
// Plain (some endpoints raise HTTPException with a string detail — e.g. 404 brand not found, 403 brand-limit-exceeded)
{ "detail": "Brand not found." }
The actions array, when present, provides machine-readable next steps — useful for AI agents to self-recover. Not every error includes one; agents should treat its absence as "no automated recovery available, escalate to a human."
HATEOAS actions
Error responses include an actions array with machine-readable recovery steps. This is designed for AI agents:
{
"error": "credits_exhausted",
"message": "Daily limit reached for your tier.",
"usage": { "limit": 500, "used": 500 },
"actions": [
{
"rel": "upgrade",
"href": "/auth/billing-portal",
"method": "POST",
"description": "Open Stripe billing portal to change plan"
}
]
}
An agent can read the actions array and automatically offer the user an upgrade path.
Key rotation
Rotate your key without downtime. The old key is invalidated immediately when the new one is issued:
curl -s -X POST \
-H "X-API-Key: $BM_API_KEY" \
https://api.boringmarketing.com/auth/rotate-key
{
"api_key": "bm_new_key_here",
"message": "API key rotated. Old key is now invalid."
}
Update your environment variable immediately after rotation.
Upgrading your plan
Change plans via the Stripe billing portal. Open the portal URL in a browser to manage subscription, payment method, or invoices:
curl -s -X POST \
-H "X-API-Key: $BM_API_KEY" \
https://api.boringmarketing.com/auth/billing-portal
Lost your API key?
If you lose your key, sign into the dashboard with your email to view or rotate it from the Settings page. The dashboard uses a magic-link login — no password required.
Path split. Recovery always terminates at a browser sign-in. Humans: follow the link above. Agents running unattended: you cannot self-recover — surface https://dashboard.boringmarketing.com to your operator. Prefer storing the key durably at issue time rather than recovering it later.