---
title: Full Pipeline
api: POST /brands/{brand_id}/analyze
description: "Run the complete intelligence pipeline end-to-end: brand enrichment, competitor discovery, keyword universe, evidence collection, opportunity synthesis, and leverage scoring."
---

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

Triggers the full pipeline in sequence. This is the simplest way to analyze a brand — one call does everything.

**Stages emitted (in order):** `brand_enrichment`, `competitor_discovery`, `family_discovery`, `evidence_collection`, `leverage_scoring`, `queue_building`. The `competitor_discovery` stage is **only emitted when the brand has no competitors set** — if the brand already has competitors, the pipeline skips that stage silently. Opportunity synthesis runs inside `evidence_collection` and does not emit its own stage string. **`/analyze` is the only pipeline trigger that emits stages**; every other endpoint runs with `meta.current_stage = null`.

**Duration:** 15-30 minutes depending on keyword universe size.

## Path parameters

<ParamField path="brand_id" type="string" required>
  The brand UUID.
</ParamField>

## Response (202 Accepted)

```json
{
  "run_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "queued"
}
```

Poll `GET /brands/{brand_id}/runs/{run_id}` for progress.

## Example

<CodeGroup>
```bash cURL
curl -X POST \
  -H "X-API-Key: $BM_API_KEY" \
  https://api.boringmarketing.com/brands/$BRAND_ID/analyze
```

```python Python
resp = httpx.post(
    f"https://api.boringmarketing.com/brands/{brand_id}/analyze",
    headers={"X-API-Key": api_key},
)
run_id = resp.json()["run_id"]
```
</CodeGroup>

<Note>
Returns `409 Conflict` if **any** other pipeline run is currently active for this brand — `/analyze` writes to every table, so it cannot run concurrently with anything else (including partial enrichment / discovery / scoring runs). Check the `X-Running-Run-Id` header for the existing run, then poll that instead. Active runs older than 15 minutes are auto-expired before the conflict check.
</Note>
