# Documentation (/) buzzabout exposes two programmatic surfaces: a public **REST API** and an **MCP server**. Both are backed by the same primitives that power the web app — datasets, dataset runs, mentions, audience datasets, audience profiles, tracking agents, pattern detections, custom parameters, and an AI assistant. ## Where to start [#where-to-start] Five minutes from API key to your first dataset and mentions list. Dataset, run, mention, audience dataset, profile, pattern — the words that come up everywhere. Wire buzzabout into Claude, Cursor, ChatGPT, or your own agent. REST endpoints, response envelope, errors, and pagination. Three chargeable categories — all per-result, all reservation-based. ## What's here [#whats-here] * **Getting started** — quickstart, concepts, authentication, pricing. * **Tutorials** — task-shaped walkthroughs combining multiple endpoints (one today: an end-to-end dataset + audience + assistant flow). * **MCP** — wire buzzabout into your assistant (Claude, Cursor, ChatGPT) in a few clicks. * **API reference** — every endpoint with an integrated playground. If you're new, start with the [quickstart](/getting-started/quickstart). If you're integrating buzzabout into an LLM workflow, jump straight to [MCP overview](/mcp/overview). ## Next steps [#next-steps] * [Quickstart](/getting-started/quickstart) — five minutes from a fresh key to your first dataset and mentions list. * [Concepts](/getting-started/concepts) — the handful of terms that show up everywhere. * [Pricing](/getting-started/pricing) — what each call costs. # Run your first analysis (/api/first-analysis) This is the API / SDK walkthrough. Prefer the no-code path? [Run your first research](/tutorials/run-your-first-research) covers the same flow inside the app. This tutorial assumes you've finished the [quickstart](/getting-started/quickstart) and have an API key. We'll chain five steps into a single workflow, all in Python: 1. Create a dataset and trigger a Reddit run. 2. Wait for it to complete. 3. Create an audience dataset, kick off a profile-collection run from the source dataset, and wait for it. 4. Read the audience profiles. 5. (Optional) Hand the dataset to the AI assistant. The whole flow takes 5–15 minutes depending on platform speed. ## Setup [#setup] ```bash export BUZZABOUT_KEY="bz_live_abcdef1234567890abcdef1234567890" pip install httpx ``` ```python title="first_analysis.py" import os import time import httpx BUZZABOUT_KEY = os.environ["BUZZABOUT_KEY"] client = httpx.Client( base_url="https://api.buzzabout.ai", headers={"x-api-key": BUZZABOUT_KEY}, timeout=30.0, ) def wait_for_run(url: str, interval: float = 10.0) -> dict: """Poll a run resource until it transitions out of pending/working.""" while True: body = client.get(url).raise_for_status().json()["data"] status_type = body["status"]["type"] print(f" {url} status: {status_type}") if status_type in ("completed", "failed"): return body time.sleep(interval) ``` ## Walkthrough [#walkthrough] ### Create the source dataset [#create-the-source-dataset] ```python dataset = client.post( "/v1/datasets", json={"name": "cold brew"}, ).raise_for_status().json()["data"] dataset_id = dataset["id"] ``` ### Trigger a run [#trigger-a-run] ```python run = client.post( f"/v1/datasets/{dataset_id}/runs", json={ "search_query": { "type": "prompt", "sources": ["reddit"], "search_query": "cold brew", }, "count": 200, "num_comments_per_post": 5, "content_analysis_actions": ["sentiment", "hook", "cta"], }, ).raise_for_status().json()["data"] run_id = run["id"] ``` ### Wait for the dataset run to complete [#wait-for-the-dataset-run-to-complete] ```python wait_for_run(f"/v1/datasets/{dataset_id}/runs/{run_id}") ``` A 200-post Reddit run with sentiment + hook + CTA analysis typically finishes in 1–3 minutes. ### List the top mentions [#list-the-top-mentions] ```python top = client.post( "/v1/mentions", json={ "dataset_ids": [dataset_id], "limit": 5, "sort": "engagement_rate", "order": "desc", "filters": [[{"type": "sentiment", "values": ["positive"]}]], }, ).raise_for_status().json()["data"] for m in top: print(f"{m['num_likes']:6,} likes {m['url']}\n {m['text'][:100]}") ``` Each row has `text`, `url`, the `author` block, engagement counts, and the analysis fields you opted in to. ### Create an audience dataset [#create-an-audience-dataset] ```python audience = client.post( "/v1/audience_datasets", json={"name": "cold brew creators"}, ).raise_for_status().json()["data"] audience_dataset_id = audience["id"] ``` ### Kick off the audience run [#kick-off-the-audience-run] ```python audience_run = client.post( f"/v1/audience_datasets/{audience_dataset_id}/runs", json={ "source_dataset_id": dataset_id, "total_profile_count": 200, }, ).raise_for_status().json()["data"] audience_run_id = audience_run["id"] ``` The audience pipeline reads the source dataset's posts (across every completed run, deduped, top by `created_at DESC`), walks authors plus commenters, and stops when 200 profiles are collected. ### Wait for the audience run [#wait-for-the-audience-run] ```python wait_for_run( f"/v1/audience_datasets/{audience_dataset_id}/runs/{audience_run_id}", interval=15.0, ) ``` Audience runs are slower than dataset runs because each profile is scraped + LLM-enriched. A 200-profile run typically takes 5–10 minutes. ### Read the audience profiles [#read-the-audience-profiles] ```python profiles = client.post( "/v1/audience_profiles", json={ "audience_dataset_ids": [audience_dataset_id], "sort": "follower_count", "order": "desc", "limit": 10, }, ).raise_for_status().json()["data"] for p in profiles: print( f"{p['follower_count']:8,} @{p['handle']:24} {p['creator_tier']}" ) ``` Each row is a creator/commenter profile with platform metadata, audience metrics, and an LLM-derived layer (`creator_tier`, `content_niche`, `interest_clusters`, `summary`, etc.). ### Hand it to the AI assistant [#hand-it-to-the-ai-assistant] The assistant returns markdown plus a typed `references[]` list. The turn is asynchronous: the POST returns 202, then you poll until `status == "completed"`. ```python ask = client.post( "/v1/ask", json={ "message": ( "In this dataset, what are the top three hooks pulling " "on Reddit? Cite specific posts." ), "context": {"dataset_ids": [dataset_id]}, }, ).raise_for_status().json()["data"] chat_id = ask["chat_id"] message_id = ask["message_id"] while True: msg = client.get( f"/v1/chats/{chat_id}/messages/{message_id}", ).raise_for_status().json()["data"] if msg["status"] in ("completed", "failed"): break time.sleep(2) print(msg["text"]) for ref in msg["references"]: print(f" -> {ref['type']} {ref['id']}") ``` Citations appear inline in `text` as `[label](post:reddit:t3_...)` markdown links. See [Reference types](/api/reference-types) for the resolution scheme. Already wired buzzabout into Claude / Cursor / ChatGPT? [Use the MCP path instead](/mcp/use-in-your-agent) — your assistant calls `buzzabout__ask` directly and you skip the polling boilerplate. Audience datasets persist independently of the source dataset. Once the audience run completes, the profiles stay accessible even if you later soft-delete the source dataset (though new audience runs can no longer target it). ## Next steps [#next-steps] * [API / Datasets](/api/endpoints/datasets) — the full parameter reference for the dataset run. * [API / Mentions](/api/endpoints/mentions) — every filter and sort option for the mentions read. * [MCP / Tools reference](/mcp/tools/reference) — every tool the host LLM can call, including `buzzabout__ask`. # API overview (/api/overview) The buzzabout REST API is a thin layer over the same primitives the web app uses. Everything you can do in the product, you can do through this API. The API is stable for `datasets`, `mentions`, `audience_datasets`, and `audience_profiles`. Newer endpoints (tracking agents, pattern detections, custom parameters, ask, chats, me) may change. We'll bump the URL prefix from `/v1` to `/v2` before any breaking change. ## Base URL [#base-url] ```text https://api.buzzabout.ai ``` All endpoints live under `/v1`. Examples in these docs use absolute URLs so they're easy to copy. ## OpenAPI spec [#openapi-spec] The full machine-readable spec is published at [`https://api.buzzabout.ai/openapi.json`](https://api.buzzabout.ai/openapi.json) — this is the same document that powers the playground on every endpoint page in these docs. Codegen-friendly; rebuilt on each release. A hosted Swagger UI lives at [`https://api.buzzabout.ai/docs`](https://api.buzzabout.ai/docs) for quick exploration outside the docs site. ## Authentication [#authentication] Every request needs an `x-api-key` header: ```http x-api-key: bz_live_abcdef1234567890abcdef1234567890 ``` See [authentication](/getting-started/auth) for key lifecycle and team semantics. ## Content type [#content-type] JSON in, JSON out. Send `Content-Type: application/json` on any request that has a body. ## Response envelope [#response-envelope] Every response is wrapped in a discriminated envelope. The top-level `status` field tells you which shape you got: ```json title="success — single resource" { "status": "success", "data": { "...": "..." } } ``` ```json title="success — paginated list" { "status": "success", "data": [ { "...": "..." }, { "...": "..." } ], "has_next": true, "cursor": "eyJjcmVhdGVkX2F0IjogMTcxNDU2NDg5MCwgImlkIjogImRzXzAxSCJ9" } ``` ```json title="error" { "status": "client_error", "error_code": "dataset_not_found", "detail": "Dataset not found", "transient": false } ``` `status` collapses the HTTP status class: | HTTP class | `status` | | ---------- | -------------- | | 1xx | `info` | | 2xx | `success` | | 3xx | `redirect` | | 4xx | `client_error` | | 5xx | `server_error` | Successful 2xx responses always carry a `data` field. Error responses carry `error_code`, `detail`, and `transient` (when `true`, retrying the same request later may succeed). A `204 No Content` returns an empty body — used for soft-deletes, pause/resume, and other no-content writes. ## Pagination [#pagination] List endpoints use cursor-based pagination. Pass `limit` and `cursor`: | Parameter | Type | Default | Notes | | --------- | ------- | ------------------------------------------------------------------------ | ------------------------------------------------------- | | `limit` | integer | `10` for resources, `20` for mentions / profiles, `25` for chat messages | 1–100. | | `cursor` | string | `null` | Opaque base64url. From `cursor` in a previous response. | | `order` | enum | `desc` | `asc` or `desc` (where supported). | The response includes `has_next: bool` and `cursor: string \| null`. When `has_next` is `false`, the cursor is `null` and the page is the last one. Cursors are sort-aware on `mentions` / `audience_profiles`. If you change the `sort` field between calls, request a fresh page (don't pass the old cursor — it'll fail validation). ## Errors [#errors] | HTTP | `error_code` examples | What it means | | ---- | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------- | | 400 | `bad_request`, `source_dataset_has_no_completed_run` | Bad request semantics or insufficient state. | | 401 | `unauthorized` | Missing or invalid `x-api-key`. | | 402 | `insufficient_credits` | Account has no credits left for this operation. See [Pricing](/getting-started/pricing). | | 404 | `dataset_not_found`, `pattern_not_found`, `chat_not_found` | Resource doesn't exist or isn't owned by the key. | | 409 | `audience_dataset_run_already_in_flight` | A pending / working run blocks a new one. | | 422 | `unprocessable_entity` | Body or query parameters are malformed. | | 429 | `too_many_requests` | See [rate limits](/api/rate-limits). | | 5xx | `internal_server_error` | Something on our end. Often paired with `transient: true`. | The `error_code` is stable — match on it, not on the human-readable `detail`. ## Account scoping [#account-scoping] Every API key carries an `account_id` (shared with the rest of the team) plus a `user_id` (the acting seat): * Account-shared resources — datasets, mentions, audience datasets, audience profiles, tracking agents, pattern detections, custom parameters — are visible to every key on the account. * **Chats are user-owned**: only the seat that created a chat can read it. Cross-seat lookups return `404`. See [Chats](/api/endpoints/chats). * Resource ids on inputs must reference resources owned by the calling account — otherwise the endpoint returns `404` instead of `403` (existence is hidden). * Global mentions and audience-profiles endpoints implicitly scope to your account; passing `dataset_ids` / `audience_dataset_ids` only narrows further. ## Endpoint groups [#endpoint-groups] Container CRUD plus async scraping runs. Scheduled re-scraping with drift alerts. Profile collection from a source dataset. Global filterable mention search. Global filterable profile search. Cross-mention narrative clustering. Bespoke LLM-extracted per-mention fields. Assistant turns plus chat history reads. `me`, usage history, current credit prices. ## Next steps [#next-steps] * [Reference types](/api/reference-types) — how assistant `references[]` and inline post links resolve. * [Rate limits](/api/rate-limits) — the per-route quotas. * [Pricing](/getting-started/pricing) — per-result credit costs. # Rate limits and quotas (/api/rate-limits) buzzabout regulates traffic through three mechanisms: a per-API-key request-rate limit, credit-based pre-checks on chargeable operations, and a per-audience-dataset concurrency cap. Build for all three in your client. ## Per-API-key request limit [#per-api-key-request-limit] Every authenticated public endpoint — REST (`/v1/...`) **and** MCP (`/mcp/...`) — is limited to **5 requests per second per API key**, shared across both surfaces. Identity is keyed on the SHA-256 digest of your API key, so a single key has one rate budget regardless of how you call the platform. Exceeding the cap returns `429`: ```json title="429 body" { "status": "client_error", "error_code": "too_many_requests", "detail": "Too many requests. Please slow down.", "transient": true, "retry_after_seconds": 0.832 } ``` The `transient: true` flag is the signal that retrying after a delay should succeed. `retry_after_seconds` is the precise wait time the limiter measured; the standard `Retry-After` HTTP header carries the same value rounded up to whole seconds. Three informational response headers are emitted on **every** authenticated request — not just `429`s — so a well-behaved client can adapt its send-rate before being throttled: | Header | Meaning | | --------------------- | ---------------------------------------- | | `RateLimit-Limit` | The cap (5 today). | | `RateLimit-Remaining` | Calls left in the current second-window. | | `RateLimit-Reset` | Seconds until the bucket fully resets. | The recommended client behaviour is exponential backoff with jitter, preferring the server's `Retry-After` hint when present: ```python title="retry.py" import random import time import httpx def call_with_backoff( client: httpx.Client, method: str, url: str, *, max_attempts: int = 5, **kwargs, ) -> httpx.Response: for attempt in range(max_attempts): resp = client.request(method, url, **kwargs) if resp.status_code != 429 or attempt == max_attempts - 1: return resp delay = float(resp.headers.get("Retry-After", "1")) delay = min(60.0, delay + random.random() * 0.25) time.sleep(delay) return resp ``` If your integration legitimately needs more than 5 req/s sustained, mail [support@buzzabout.ai](mailto:support@buzzabout.ai). Enterprise plans are eligible for a higher per-key rate without a code change on your side. ## Credit-based pre-checks (402) [#credit-based-pre-checks-402] Runs (`POST /v1/datasets/{id}/runs`, `POST /v1/audience_datasets/{id}/runs`, `POST /v1/pattern_detections`, `POST /v1/custom_parameters/{id}/runs`) all pre-check the account's credit balance. When it would be exceeded the response is `402` with a structured `detail` block telling you exactly how many credits the call needs and how many you have: ```json title="402 body" { "status": "client_error", "error_code": "insufficient_credits", "detail": { "message": "Insufficient credits to start this run.", "required": "20", "available": "0", "top_up_url": "https://app.buzzabout.ai/settings/billing" }, "transient": false } ``` Surface `required - available` in your UI so the user knows exactly how many credits to top up. Top up via the web app or upgrade your plan; in-flight requests are never partially charged. See [Pricing](/getting-started/pricing#reservation-based-charging) for how the up-front reservation works. ## Per-audience-dataset concurrency (409) [#per-audience-dataset-concurrency-409] A single audience dataset only allows one in-flight run. A second `POST /v1/audience_datasets/{id}/runs` while another is pending or working returns `409`: ```json { "status": "client_error", "error_code": "audience_dataset_run_already_in_flight", "detail": "Another run for this audience dataset is already pending or working. Wait for it to finish before starting a new one.", "transient": false } ``` Poll `GET /v1/audience_datasets/{id}/runs/{run_id}` until the existing run terminates, then retry. Regular datasets do not have this restriction — multiple runs can be in flight concurrently against the same dataset. ## Next steps [#next-steps] * [Pricing](/getting-started/pricing) — per-result credit costs and reservation-based charging. * [API overview](/api/overview) — response envelope, errors, and pagination model. # Reference types (/api/reference-types) Assistant messages and tracking-agent alerts return text alongside a **`references[]`** array — a flat list of typed pointers into buzzabout resources. Six entries point to entities the assistant produced or referenced (datasets, patterns, …); a seventh, **`post`**, captures every inline post citation in the assistant's prose. This page enumerates both shapes and the endpoint you call to resolve each one. ## Entity references [#entity-references] Each entity entry in `references[]` is: ```json { "type": "", "id": "" } ``` No inline payload — clients fetch the underlying resource on demand. In practice an assistant turn returns ≤3 entity references; the round-trip cost is negligible and the contract stays stable as the underlying resources grow. | `type` | Resolution endpoint | | ----------------------- | -------------------------------------------------------------------------------------------- | | `pattern` | [`GET /v1/patterns/{pattern_id}`](/api/endpoints/patterns) | | `research_preview` | `GET /v1/research_previews/{preview_id}` (hidden; resolved via the assistant directly) | | `dataset` | [`GET /v1/datasets/{dataset_id}`](/api/endpoints/datasets) | | `audience_dataset` | [`GET /v1/audience_datasets/{audience_dataset_id}`](/api/endpoints/audience-datasets) | | `custom_parameter_run` | [`GET /v1/custom_parameters/{parameter_id}/runs/{run_id}`](/api/endpoints/custom-parameters) | | `pattern_detection_run` | [`GET /v1/pattern_detections/{run_id}`](/api/endpoints/pattern-detections) | `research_preview` is the working pointer to an in-progress assistant investigation — the assistant resolves it for you in subsequent turns, so most clients never need to expand it directly. ## Post references [#post-references] Every **unique** post the assistant cites surfaces as a structured `post` entry in `references[]` — **not** as inline markdown: ```json { "type": "post", "source": "tiktok", "id": "7392184932", "citation": 1, "start_index": 4, "end_index": 7 } ``` In the returned `text`, each `[label](post:source:id)` markdown link is rewritten to a bracketed numeric marker — `[1]`, `[2]`, … — academic-citation style. Numbering is **per-message** and assigned in first-appearance order over unique `(source, id)` tuples: the first unique post cited gets `1`, the second unique post gets `2`, and so on. **Re-citing the same post in the same message re-uses its number** — every occurrence of that post in the prose shows the same `[N]` marker, and the post appears once in `references[]`. The half-open `start_index` / `end_index` pair points at the **first** `[N]` occurrence in the rewritten `text`: slicing `text[start_index:end_index]` returns exactly `f"[{citation}]"` (e.g. `"[1]"`). To locate every occurrence of a citation, string-search the text for the literal bracketed marker. Example: an assistant turn that cites a TikTok post twice and a Reddit post once produces this shape: ```text text: "See [1] and [2]. As [1] also notes…" references: [ { "type": "post", "source": "tiktok", "id": "7392184932", "citation": 1, "start_index": 4, "end_index": 7 }, { "type": "post", "source": "reddit", "id": "t3_1oqliu8", "citation": 2, "start_index": 12, "end_index": 15 } ] ``` To resolve `(source, id)` back to a full mention payload, call [`POST /v1/mentions`](/api/endpoints/mentions) with `post_refs`: ```json { "post_refs": [ { "source": "tiktok", "id": "7392184932" }, { "source": "reddit", "id": "t3_1oqliu8" } ] } ``` The response carries the same `Mention` shape as a standard list query. The same `(source, id)` cited twice in a message produces **one** `post` reference with **one** `citation` number; both occurrences in the rewritten text use the same `[N]` marker. Offsets on the ref point at the first occurrence — search the text for `[N]` to find every reference site. Posts can appear dozens of times in a single answer; rendering them as full entity records would balloon the response. Surfacing each unique post as a slim `{source, id, citation, start_index, end_index}` ref keeps the payload small while letting clients render every citation as a precise link without parsing markdown themselves. ## Where this surface appears [#where-this-surface-appears] * **`POST /v1/ask`** (and the same surface via MCP) — assistant turns. The reply text and `references[]` come back via [`GET /v1/chats/{chat_id}/messages/{message_id}`](/api/endpoints/chats) once the turn settles. * **`GET /v1/chats/{id}/messages`** — historical assistant messages carry the same shape per row. * **`GET /v1/tracking_agents/{id}/messages`** — alerts produced by a running tracking agent. Each alert's `text` carries inline post citations as `post` references and, for cross-dataset patterns, a `pattern` entity reference. `tracking_agent` is **not** a reference type — agents are entities you manage, not citations the assistant produces. ## Next steps [#next-steps] * [Mentions](/api/endpoints/mentions) — `post_refs` lookup path for resolving cited posts back to the full mention shape. * [Patterns](/api/endpoints/patterns) — what's behind a `pattern` reference once you expand it. * [POST /v1/ask](/api/endpoints/ask) — where references first appear in a typical agent loop. # Authentication (/getting-started/auth) buzzabout authenticates REST and MCP requests with the same primitive: a long-lived **API key** sent in the `x-api-key` header. Interactive assistants (Claude) can additionally exchange an OAuth flow for a JWT on the MCP surface — see [MCP / Authentication](/mcp/auth) for that path. The REST API is api-key only. ## API keys [#api-keys] API keys live under **Settings → API keys** in the web app. Click *New key*, name it, copy the value — it's shown once. Keys look like `bz_live_<32 hex>` (for example, `bz_live_abcdef1234567890abcdef1234567890`). They're tied to one buzzabout account; every request the key makes is scoped to that account's resources. buzzabout stores only the **SHA-256 digest** of your key, not the key itself. Once you close the *New key* dialog the raw value cannot be retrieved — not by you, not by buzzabout support, not even with database access. If you lose it, revoke the lost key and mint a new one. Treat the dialog as a one-shot prompt: copy the value into your secret manager before dismissing it. ### Sending the key [#sending-the-key] ```http GET /v1/datasets HTTP/1.1 Host: api.buzzabout.ai x-api-key: bz_live_abcdef1234567890abcdef1234567890 ``` ```bash title="cURL" curl "https://api.buzzabout.ai/v1/datasets" \ -H "x-api-key: $BUZZABOUT_KEY" ``` ### Verifying a key [#verifying-a-key] Hit `GET /v1/datasets` with `limit=1`. A 200 means the key is valid. A 401 means it's missing or revoked. ```bash curl -s -o /dev/null -w "%{http_code}\n" \ "https://api.buzzabout.ai/v1/datasets?limit=1" \ -H "x-api-key: $BUZZABOUT_KEY" ``` ### Rotation [#rotation] There's no built-in expiration. Rotate by: 1. Creating a new key under **Settings → API keys**. 2. Updating your environment / secret store. 3. Revoking the old key. Revoked keys return `401` immediately on the next call — there's no grace period. ## Team-member semantics [#team-member-semantics] Most resources (datasets, mentions, audience datasets, audience profiles, tracking agents, pattern detections, custom parameters) are **account-shared** — visible to every key on the same account regardless of which seat minted them. The one exception: **chats are per-seat**. A chat is only readable by the seat that created it; other team members get a `404` from [`GET /v1/chats/{id}`](/api/endpoints/chats). If you revoke a seat, their keys are deleted with them — plan to rotate any service-account-style integrations through a seat you keep. ## Using API keys with MCP [#using-api-keys-with-mcp] The MCP server accepts the same `x-api-key` header as the REST API. Headless agents — your own SDK-built integration, a server-side LLM pipeline — authenticate this way without going through OAuth. See [MCP / Authentication](/mcp/auth) for the headless wiring and the OAuth-for-interactive-hosts path. ## Next steps [#next-steps] * [Quickstart](/getting-started/quickstart) — first dataset, first mentions. * [MCP authentication](/mcp/auth) — `x-api-key` and OAuth/JWT paths for the MCP server. * [Pricing](/getting-started/pricing) — what each authenticated call costs. # Concepts (/getting-started/concepts) A handful of terms come up everywhere in the product, the API, and the MCP server. Knowing them makes the rest of the docs much shorter to read. ## Dataset [#dataset] A **dataset** is a named container of mentions. You create one, kick off one or more runs against it, and the runs populate it with posts. The same dataset can hold posts from many runs over time. ```json { "id": "ds_2NK4Y3JxhJ8r9c0Y1u5lkjm9Wb1", "name": "cold brew", "mentions_count": 1247, "created_at": "2026-05-01T12:00:00Z", "url": "https://app.buzzabout.ai/datasets/ds_2NK4Y3JxhJ8r9c0Y1u5lkjm9Wb1" } ``` ## Dataset run [#dataset-run] A **dataset run** is one snapshot of post collection: a search query (or list of URLs), a target post count, optional date range, and the analysis steps to apply. Runs are asynchronous — they return `pending` and transition through `working` to `completed` (or `failed`). ```json { "id": "dr_2NK4ZG2BqkAxK1nT0RZyZbBmZHN", "dataset_id": "ds_2NK4Y3JxhJ8r9c0Y1u5lkjm9Wb1", "status": { "type": "completed", "steps": [ { "name": "scraping", "completed_at": 1714564890 }, { "name": "analysis", "completed_at": 1714565010 } ] }, "params": { "search_query": { "type": "prompt", "sources": ["reddit"], "search_query": "cold brew" }, "count": 200 }, "mentions_count": 200 } ``` ## Mention [#mention] A **mention** is a single post in a dataset — a Reddit thread, a TikTok video, a YouTube video, an Instagram reel, a LinkedIn post. The same post can appear in multiple datasets (the `datasets` field on each mention enumerates them). Each mention carries author metadata, engagement counts (views, likes, comments, shares), the original URL, attached media, and a structured content-analysis layer (sentiment, emotions, hook, CTA, tone of voice, content topics, mentioned brands, entities, and more) when those analyses ran on the parent run. ## Audience dataset [#audience-dataset] An **audience dataset** is a name-only container for audience runs — runs that collect creator and commenter profiles from the posts in a source dataset. Different audience runs in the same container can target different source datasets over time. ```json { "id": "ad_2NK4ahFr1tJgX9Lp2dQ5mWvNcR8", "name": "cold brew creators", "profile_count": 300 } ``` ## Audience profile [#audience-profile] A **profile** is one creator or commenter from one audience run. It captures the platform-specific account metadata (username, display name, avatar, verification, follower / following / content counts, bio, external link), an LLM-derived audience layer (creator tier, niche, interest clusters, brand affinities, communication tone, OCEAN personality scores, summary), and a normalised authenticity / engagement profile. ## Pattern [#pattern] A **pattern** is a recurring shape across mentions — a hook framing, a narrative arc, a visual motif. Patterns are produced by [pattern-detection runs](/api/endpoints/pattern-detections) and by [tracking agents](/api/endpoints/tracking-agents); the resulting `pattern_id` is retrievable via [`GET /v1/patterns/{id}`](/api/endpoints/patterns). ## Next steps [#next-steps] * [API / Datasets](/api/endpoints/datasets) — the API equivalents of every concept on this page. * [Quickstart](/getting-started/quickstart) — five minutes from zero to a finished dataset. # Pricing (/getting-started/pricing) buzzabout charges credits per result, not per request. The four public categories below cover every chargeable endpoint and MCP tool. Asking the assistant (`POST /v1/ask` and the `buzzabout__ask` MCP tool) is free. A **credit** is the smallest billable unit; one US dollar buys 1,000 of them on the standard plan (your exact rate is on [buzzabout.ai/pricing](https://buzzabout.ai/pricing)). ## Categories [#categories] | Category | Credits | When | Reservation-based? | | -------------------- | ------- | --------------------------------------------------------------------------------------- | ------------------ | | `mention` | 1 | per mention collected (dataset run or tracking-agent re-scrape — unified for clients) | yes | | `audience_profile` | 3 | per profile collected by an audience-dataset run | yes | | `post_processing` | 0.5 | per post processed by a custom-parameter run or pattern-detection run | yes | | `preview_generation` | 1 | per source in a research preview (`url` previews refund profiles that returned no data) | yes | Custom-parameter `preview` is free, and so is asking the assistant (`POST /v1/ask` / `buzzabout__ask`). ## Reservation-based charging [#reservation-based-charging] Every chargeable category on the public surface is **reservation-based**. The endpoint reserves credits up-front based on the upper-bound result count (e.g. the requested `count` on a dataset run, the source dataset's mention count for a custom-parameter or pattern-detection run), then refunds any unused credits when the run completes. A run that asked for 500 mentions and returned 412 leaves you charged for 412 — the remaining 88 credits go back to your balance. A [research preview](/api/endpoints/research-previews) reserves 1 credit per source; `url` previews refund any source that returned no data (private, non-existing, or failed to retrieve). If a run **fails** before it can produce results, the full reservation is refunded. Endpoints that would exceed your balance return `402 insufficient_credits` before any work starts. Top up via the web app or upgrade your plan; in-flight requests are never partially charged. ## Programmatic access [#programmatic-access] The current price table is available without authentication at [`GET /v1/credit_prices`](/api/endpoints/credit-prices): ```bash curl https://api.buzzabout.ai/v1/credit_prices ``` ```json { "status": "success", "data": { "mention": 1, "audience_profile": 3, "post_processing": 0.5, "preview_generation": 1 } } ``` Use this to build cost dashboards, pre-flight budget checks, or to ground a cost-conscious agent's reasoning. The shape is flat and the keys are stable — new categories ship as additive fields. ## Usage history [#usage-history] `GET /v1/me/usage_history` returns a paginated feed of charges and refunds against your account. Each item carries the category, the endpoint that produced it, and the delta in credits. See [`/v1/me`](/api/endpoints/me). The feed may include legacy category strings (`ai_assistant`, `REPORT`, `AUDIENCE_RESEARCH`, `TRACKING`) for charges that pre-date this surface — those endpoints aren't part of the public API but their history shows up on the same feed. In particular, `ai_assistant` belongs to the assistant in the previous version of the app; asking the assistant today (`POST /v1/ask` / `buzzabout__ask`) is free, so the category appears only on old rows. ## Next steps [#next-steps] * [`GET /v1/credit_prices`](/api/endpoints/credit-prices) — programmatic price lookup. * [`GET /v1/me`](/api/endpoints/me) — current balance + usage history. * [API overview](/api/overview) — once you know what each call costs. # Quickstart (/getting-started/quickstart) Spin up your first dataset in five minutes. By the end you'll have an API key, a dataset populated with real social-media posts, and a list of mentions you can work with. This guide uses **Python with `httpx`**. The same calls work from any language — pick the request library you prefer. Every example URL, header, and body shape is identical. ## Prerequisites [#prerequisites] * A buzzabout account ([sign up](https://buzzabout.ai/?signup) — the free tier is enough for this walkthrough). * Python 3.10+ and `httpx` (`pip install httpx`). Paste this whole page (or [`https://docs.buzzabout.ai/llms-full.txt`](https://docs.buzzabout.ai/llms-full.txt)) into Claude Code, Cursor, or any coding assistant and ask it to wire buzzabout into your project. The docs are written to be agent-readable. ## Walkthrough [#walkthrough] ### Get an API key [#get-an-api-key] Open **Settings → API keys** in the web app and click *New key*. Copy the value (it starts with `bz_live_`) somewhere safe — you'll see it once. ```bash export BUZZABOUT_KEY="bz_live_abcdef1234567890abcdef1234567890" ``` buzzabout only stores the SHA-256 digest of your key, not the key itself. Once you close the "New key" dialog the raw value is gone — if you lose it, mint a new one. See [authentication](/getting-started/auth) for the full lifecycle. If you're integrating buzzabout with Claude, Cursor, ChatGPT, or another assistant instead of calling the API yourself, skip ahead to [Use in your agent](/mcp/use-in-your-agent). You'll wire the buzzabout MCP server into your assistant and let it drive the same workflow. ### Set up the client [#set-up-the-client] A tiny helper to keep the rest of the code clean: ```python title="quickstart.py" import os import time import httpx BUZZABOUT_KEY = os.environ["BUZZABOUT_KEY"] BASE_URL = "https://api.buzzabout.ai" client = httpx.Client( base_url=BASE_URL, headers={"x-api-key": BUZZABOUT_KEY}, timeout=30.0, ) ``` ### Create a dataset [#create-a-dataset] A **dataset** is a named container for the mentions you'll collect. ```python title="quickstart.py (continued)" response = client.post("/v1/datasets", json={"name": "cold brew"}) response.raise_for_status() dataset = response.json()["data"] dataset_id = dataset["id"] print(f"Created dataset {dataset_id}: {dataset['name']}") ``` ```json title="201 Created" { "status": "success", "data": { "id": "ds_2NK4Y3JxhJ8r9c0Y1u5lkjm9Wb1", "name": "cold brew", "created_at": "2026-05-01T12:00:00Z" } } ``` ### Trigger a dataset run [#trigger-a-dataset-run] A **run** is what actually collects posts from social platforms. The call is asynchronous — it returns `202 Accepted` immediately with a run in `pending` state. ```python title="quickstart.py (continued)" response = client.post( f"/v1/datasets/{dataset_id}/runs", json={ "search_query": { "type": "prompt", "sources": ["reddit", "tiktok"], "search_query": "cold brew coffee", }, "count": 200, }, ) response.raise_for_status() run = response.json()["data"] run_id = run["id"] print(f"Queued run {run_id} — status: {run['status']['type']}") ``` ```json title="202 Accepted" { "status": "success", "data": { "id": "dr_2NK4ZG2BqkAxK1nT0RZyZbBmZHN", "dataset_id": "ds_2NK4Y3JxhJ8r9c0Y1u5lkjm9Wb1", "status": { "type": "pending", "steps": [] }, "created_at": "2026-05-01T12:00:30Z" } } ``` ### Poll until completed [#poll-until-completed] ```python title="quickstart.py (continued)" while True: response = client.get(f"/v1/datasets/{dataset_id}/runs/{run_id}") response.raise_for_status() status = response.json()["data"]["status"]["type"] print(f" status: {status}") if status in ("completed", "failed"): break time.sleep(10) ``` A 200-post run typically takes 1–3 minutes. ```json title="200 OK (completed)" { "status": "success", "data": { "id": "dr_2NK4ZG2BqkAxK1nT0RZyZbBmZHN", "dataset_id": "ds_2NK4Y3JxhJ8r9c0Y1u5lkjm9Wb1", "status": { "type": "completed", "steps": [ { "name": "scraping", "completed_at": 1714564890 }, { "name": "analysis", "completed_at": 1714565010 } ] }, "mentions_count": 200, "created_at": "2026-05-01T12:00:30Z", "updated_at": "2026-05-01T12:03:30Z" } } ``` ### List mentions [#list-mentions] Mentions are global — `POST /v1/mentions` returns all the mentions across every dataset you own. Pass `dataset_ids` to scope the search. ```python title="quickstart.py (continued)" response = client.post( "/v1/mentions", json={ "dataset_ids": [dataset_id], "limit": 5, "sort": "engagement_rate", "order": "desc", }, ) response.raise_for_status() mentions = response.json()["data"] for m in mentions: print(f" {m['source']:9} {m['num_likes']:6,} likes {m['url']}") ``` ```json title="200 OK" { "status": "success", "data": [ { "source": "reddit", "id": "t3_1oqliu8", "author": { "title": "u/sipdaily", "url": "https://www.reddit.com/user/sipdaily/", "follower_count": 1240 }, "text": "Nobody tells you that nitro cold brew tastes...", "url": "https://www.reddit.com/r/coffee/comments/1oqliu8/", "num_views": 12400, "num_likes": 248, "engagement_rate": "0.020", "datasets": [ { "id": "ds_2NK4Y3JxhJ8r9c0Y1u5lkjm9Wb1", "name": "cold brew" } ] } ], "has_next": true, "cursor": "eyJzb3J0X3ZhbHVlIjogIjAuMDIwIiwgImlkIjogInQzXzFvcWxpdTgifQ==" } ``` ## Next steps [#next-steps] * [Run your first analysis](/api/first-analysis) — end-to-end walkthrough including audience scraping and the AI assistant. * [MCP overview](/mcp/overview) — drive the same workflow from an MCP-capable assistant (Claude, Cursor, ChatGPT, your own SDK). * [API / Datasets](/api/endpoints/datasets) — the full reference for the calls we just made. # Authentication (/mcp/auth) The buzzabout MCP server accepts two ways to authenticate on the same URL. Which one you use depends on your client — both end up as the same buzzabout account, and every tool (including `ask`) works either way. | Your client | Use | What you do | | --------------------------------------------- | --------------- | ------------------------------------------------------ | | **Claude Desktop, Claude.ai, ChatGPT** | **OAuth** | Click **Connect** / sign in — nothing to copy or store | | **Claude Code, Codex, Cursor**, custom agents | **`x-api-key`** | Paste a key into the client's MCP config | Standard interactive assistants ship an OAuth flow, so use that — there's no reason to manage an API key for them. The CLI / IDE agents (Claude Code, Codex, Cursor) don't have a built-in OAuth flow for custom MCP servers, so they use an API key. Per-client setup snippets are on [Use in your agent](/mcp/use-in-your-agent); this page is just the auth choice and how to supply a key. ## Endpoint [#endpoint] ```text https://mcp.buzzabout.ai/mcp/ ``` Streamable HTTP. The trailing slash is required. ## OAuth — standard assistants [#oauth--standard-assistants] Claude Desktop, Claude.ai, and ChatGPT handle OAuth for you. Add the server URL in the client's connector settings and **Connect** / sign in with your buzzabout account; the client stores the token and replays it on every call. You don't configure anything else, and you don't need an API key. The per-client steps are on [Use in your agent](/mcp/use-in-your-agent). ## `x-api-key` — Claude Code, Codex, Cursor, custom [#x-api-key--claude-code-codex-cursor-custom] Mint a key in the web app under `Settings → API keys → New key`. Copy the value (`bz_live_...`) once — it's hashed and can't be retrieved again. (Lifecycle and rotation: [REST authentication](/getting-started/auth).) Then add it to your client's MCP config as an `x-api-key` header — see [Use in your agent](/mcp/use-in-your-agent) for the exact config per client. For a custom agent, send the header on every request: ```http POST /mcp/ HTTP/1.1 Host: mcp.buzzabout.ai x-api-key: bz_live_... Content-Type: application/json Accept: application/json,text/event-stream ``` ```python title="agent.py" import asyncio import os from mcp import ClientSession from mcp.client.streamable_http import streamablehttp_client async def main() -> None: headers = {"x-api-key": os.environ["BUZZABOUT_KEY"]} async with streamablehttp_client( "https://mcp.buzzabout.ai/mcp/", headers=headers, ) as (read, write, _): async with ClientSession(read, write) as session: await session.initialize() tools = await session.list_tools() for tool in tools.tools: print(tool.name) asyncio.run(main()) ``` ```ts title="agent.ts" import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; const transport = new StreamableHTTPClientTransport( new URL("https://mcp.buzzabout.ai/mcp/"), { requestInit: { headers: { "x-api-key": process.env.BUZZABOUT_KEY! }, }, }, ); const client = new Client({ name: "my-agent", version: "0.1.0" }); await client.connect(transport); ``` ```bash curl -X POST https://mcp.buzzabout.ai/mcp/ \ -H "x-api-key: $BUZZABOUT_KEY" \ -H "Content-Type: application/json" \ -H "Accept: application/json,text/event-stream" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/list" }' ``` ## Tool errors [#tool-errors] Tool errors come back inside the result as a structured JSON payload — the same `error_code` taxonomy as the REST API: ```json { "error": { "code": "dataset_not_found", "message": "Dataset not found", "status": 404 } } ``` Match on `code` (stable); show `message` to humans. `status` mirrors the HTTP status the equivalent REST call would return. Transport errors (`401`, `429`) come back as MCP transport errors, not tool-level errors — handle them at the SDK transport layer. ## Next steps [#next-steps] * [Use in your agent](/mcp/use-in-your-agent) — per-client setup. * [Tools reference](/mcp/tools/reference) — every tool, with parameters. * [REST authentication](/getting-started/auth) — the same `bz_live_...` key works for `https://api.buzzabout.ai/v1/*`. # MCP overview (/mcp/overview) The buzzabout **MCP server** lets any [Model Context Protocol](https://modelcontextprotocol.io/) client — Claude Desktop, Claude.ai, Claude Code, Codex, Cursor, ChatGPT, your own SDK agent — drive buzzabout from inside the conversation. You ask a research question; the assistant collects and analyses the posts and returns the answer in the chat, often as an interactive widget. ## Transport [#transport] ```text https://mcp.buzzabout.ai/mcp/ ``` Streamable HTTP — one URL handles tool listing, tool calls, and OAuth discovery (no separate SSE endpoint). Use `https://mcp.buzzabout.ai/mcp/` (with trailing slash). The unslashed `/mcp` returns a `307` redirect that strips the request body in many MCP clients, which surfaces as silent connection failures or empty tool lists. See [Use in your agent](/mcp/use-in-your-agent) for per-host wiring. ## Authentication [#authentication] Two paths on the same URL, chosen by client: * **OAuth** — standard assistants (Claude Desktop, Claude.ai, ChatGPT) handle it; just sign in. * **`x-api-key`** — CLI / IDE agents (Claude Code, Codex, Cursor) and custom agents paste a key. Both resolve to the same buzzabout account. Full details on [Authentication](/mcp/auth). ## What's exposed [#whats-exposed] **15 tools**, in two kinds: * **The assistant flow — `ask` → `get_message` → `render`.** `ask` hands a prompt to the buzzabout assistant, which writes the query, previews it, collects posts, profiles audiences, detects patterns, and analyses. It returns immediately; you poll `get_message` for the answer, and `render` draws any rich block as an interactive widget. * **Read-only lookups** — list / fetch datasets, runs, mentions, audience profiles, and tracking agents the account already has. There are **no create / update / delete tools** — collecting, profiling, and pattern detection all happen through `ask`. For programmatic CRUD, use the [REST API](/api/overview). Every tool — the assistant flow plus the read-only lookups. Per-host wiring + a first prompt to test the loop. OAuth for standard assistants, `x-api-key` for Claude Code / Codex / Cursor. ## The async flow [#the-async-flow] Research can run for minutes — past a host's tool-call timeout — so the assistant flow is asynchronous: 1. `buzzabout__ask(prompt)` returns immediately with `{ chat_id, message_id, status: "working" }`. 2. The host polls `buzzabout__get_message(chat_id, message_id)` — which **long-polls** (one call holds for \~45s, returning the moment the turn settles) — until `stop_reason` is non-null. 3. The answer comes back as `blocks`: a `render: true` block is shown via `buzzabout__render` as an interactive MCP App widget; a `render: false` block carries plain markdown the host relays. Hosts without the MCP Apps UI extension get markdown throughout. Credit-exhausted accounts surface a `402` with a structured "need N more credits" body so the assistant can ask the user to top up before retrying. ## When to use MCP vs REST [#when-to-use-mcp-vs-rest] | Use MCP | Use REST | | ------------------------------------------------ | ------------------------------------------ | | Interactive — a person talking to an LLM client. | Batch — scheduled job, cron-driven sync. | | You want the assistant to drive the research. | You're writing the orchestration yourself. | | Results inline in the conversation. | You need primitive CRUD / precise control. | Both surfaces are backed by the same primitives, so hybrid setups work naturally (e.g. run a heavy collection via REST, then ask an MCP-capable assistant to summarise it). ## See also [#see-also] * [Agentation](https://agentation.dev/) — third-party MCP devtool that pairs well with buzzabout for local agent development and inspection. ## Next steps [#next-steps] * [Tools reference](/mcp/tools/reference) — the 15-tool surface. * [Use in your agent](/mcp/use-in-your-agent) — per-host wiring + first prompt. * [Authentication](/mcp/auth) — pick OAuth or `x-api-key`. # Use in your agent (/mcp/use-in-your-agent) The buzzabout MCP server speaks the Model Context Protocol — any MCP-capable client can connect. This page covers the wiring for the common hosts plus the SDK pattern for your own agent. ## Endpoint [#endpoint] ```text https://mcp.buzzabout.ai/mcp/ ``` Streamable HTTP. Trailing slash required. Standard assistants (Claude Desktop/web, ChatGPT) authenticate via **OAuth**; CLI / IDE agents (Claude Code, Codex, Cursor) and custom agents use an **`x-api-key`** header — see [Authentication](/mcp/auth). ## Wire it up [#wire-it-up] `Settings → Connectors → Add custom connector` (the `+`). Fill in: | Field | Value | | ----- | ------------------------------- | | Name | `buzzabout` | | URL | `https://mcp.buzzabout.ai/mcp/` | The connector is added under **Not connected**. Click **Connect** to sign in with your buzzabout account (OAuth) — it stays disconnected and unusable until you do. Once connected, Claude replays the token on later calls. Identical on Claude Desktop and Claude.ai (web). Claude Code uses an API key (no OAuth flow for custom MCP servers). Mint a key under `Settings → API keys` in the buzzabout web app, then: ```bash claude mcp add --transport http buzzabout \ https://mcp.buzzabout.ai/mcp/ \ --header "x-api-key: bz_live_..." ``` Check it's connected with `claude mcp list`. Add an entry to `~/.codex/config.toml`. Codex authenticates a streamable-HTTP server with static headers — use `x-api-key` (mint a key under `Settings → API keys` in the buzzabout web app): ```toml title="~/.codex/config.toml" [mcp_servers.buzzabout] url = "https://mcp.buzzabout.ai/mcp/" http_headers = { "x-api-key" = "bz_live_..." } ``` `Settings → Cursor Settings → MCP → Add new MCP server`. ```json title="~/.cursor/mcp.json" { "mcpServers": { "buzzabout": { "transport": "streamable-http", "url": "https://mcp.buzzabout.ai/mcp/", "headers": { "x-api-key": "bz_live_..." } } } } ``` Cursor doesn't ship an OAuth flow for custom MCP servers — use the `x-api-key` path. Mint a key under `Settings → API keys` in the buzzabout web app. `Settings → Apps → Create app`, enter the server URL, and select **OAuth** as the auth mechanism. Requires a paid ChatGPT plan (Plus, Pro, or Team). | Field | Value | | ----- | ------------------------------- | | URL | `https://mcp.buzzabout.ai/mcp/` | | Auth | OAuth | ChatGPT then prompts you to sign in with your buzzabout account. For your own agent, use one of the official MCP SDKs with an `x-api-key` header. ```python title="agent.py" import asyncio import os from mcp import ClientSession from mcp.client.streamable_http import streamablehttp_client async def main() -> None: headers = {"x-api-key": os.environ["BUZZABOUT_KEY"]} async with streamablehttp_client( "https://mcp.buzzabout.ai/mcp/", headers=headers, ) as (read, write, _): async with ClientSession(read, write) as session: await session.initialize() tools = await session.list_tools() print(f"loaded {len(tools.tools)} buzzabout tools") result = await session.call_tool( "buzzabout__list_datasets", arguments={"limit": 5}, ) print(result.content) asyncio.run(main()) ``` ## A first prompt [#a-first-prompt] Once the server is wired in, try this in any MCP-capable assistant: > *"Research how people talk about cold brew on Reddit over the last > week, then show me the top themes and a few representative posts."* The assistant routes the whole thing through `buzzabout__ask` — it writes the query, previews it, collects posts, and analyses — then polls for the answer: 1. `buzzabout__ask("research cold brew on Reddit, last week …")` → returns `{ chat_id, message_id, status: "working" }` immediately. 2. `buzzabout__get_message(chat_id, message_id)` — long-polls until `stop_reason` is non-null (the run can take a few minutes). 3. Walk the returned `blocks`: a `render: true` block → `buzzabout__render(message_id, block_id)` shows it as an interactive widget; a `render: false` block carries its `text` to relay. 4. Continue the thread by passing the `chat_id` back into `ask`. Every tool call is visible in the host's UI — inspect what was sent and what came back. Collecting datasets, profiling audiences, and detecting patterns all happen **through `ask`** — there are no direct CRUD tools. The other tools are read-only lookups. See the [tools reference](/mcp/tools/reference). ## Troubleshooting [#troubleshooting] **Trailing slash?** `https://mcp.buzzabout.ai/mcp/` (with trailing slash). The unslashed `/mcp` redirects via 307, which strips the request body in many MCP clients. **Auth?** OAuth tokens expire — sign out and back in via the host's integration settings. API keys can be revoked via the buzzabout web app's `Settings → API keys`. **Plan?** MCP access is gated by plan tier — your plan must include MCP. `GET /v1/me` reports whether it's enabled. If the tools list is empty, the most likely causes are: 1. **Auth failure** — sign out and back in (OAuth) or check key validity (`x-api-key`). 2. **Network proxy / firewall** — outbound HTTPS to `mcp.buzzabout.ai/mcp/` is blocked. 3. **Account lacks access** — your plan must include MCP. Still stuck? Mail [support@buzzabout.ai](mailto:support@buzzabout.ai) with the request id from any failed call. ## Next steps [#next-steps] * [Tools reference](/mcp/tools/reference) — every tool the surface exposes. * [Authentication](/mcp/auth) — which auth method per client. * [MCP overview](/mcp/overview) — the surface at a glance. # Analyse feature requests (/tutorials/analyse-feature-requests) If you build a B2B SaaS project-management tool, your roadmap signals are already sitting in public conversations — people asking "please add a Gantt view", "why doesn't it support SAML SSO", "I wish it integrated with Slack". The **Feature requests** skill reads a dataset you've already collected, pattern-matches those product-directed asks, ranks them by severity, and triages each one, so a stream of social chatter becomes a prioritised product backlog you can hand to your product team. ## Prerequisites [#prerequisites] * A dataset with a completed collection run, so there are posts and comments to analyse. See [Set up your project](/tutorials/set-up-your-project) and [Create a mention collection](/tutorials/create-a-mention-collection). * A chat or project open against that dataset — a skill can only be applied inside one. If you're new to applying skills, start with [Use skills](/tutorials/use-skills). ## Walkthrough [#walkthrough] ### Open skills [#open-skills] Open a chat scoped to the dataset that holds your project-management mentions. In the chat composer, click the **Open skills** book icon to pop the skills picker. The skill needs a draft to attach to, so make sure you're inside a chat or project. ### Browse all skills [#browse-all-skills] The picker opens on **Favourite skills**. Click **Browse all skills** in the footer to open the full library, then open the **Content & positioning** or **Audience research** category in the left **Skills** sidebar — **Feature requests** is tagged under both. You can also type into the search box to jump straight to it by name. ### Use this skill [#use-this-skill] Click the **Feature requests** row to open its detail view and read the instructions. When it's the right one, click **Use this skill** to attach it to the current chat draft. The modal closes and the skill appears as a chip in the composer. ### Add context and send [#add-context-and-send] Attach the project-management dataset as context in the composer before you send — the skill chip and the context chip sit independently on the same message. Type a short prompt such as `What features are people asking us to add?` and send. The skill's instructions tell the assistant to scan for product-directed asks, so you don't need to spell out the method. ### Let the pattern pipeline run [#let-the-pattern-pipeline-run] To separate genuine feature requests from general complaints, the skill runs the async pattern-matching pipeline over your mentions. This takes a few minutes and may charge credits. If the dataset already has pattern assignments from an earlier run, the assistant reuses them instead of re-detecting, so a second pass is faster and cheaper. The pipeline pattern-matches asks like "please add X" and "why doesn't it support Y" and deliberately keeps them **distinct from pain points** (problems with what already exists) and **objections** (reasons not to buy). It ranks each request by severity and triages it: requests that are **already solved** are flagged as a discoverability gap rather than a build, and the rest are scoped **small**, **medium**, or **large**. ### Read the severity bar and request table [#read-the-severity-bar-and-request-table] The assistant returns the standard skill artifact: a hero chart, a detail table, and three takeaways. The hero chart is a **severity bar** colour-coded by recommended action — **ship**, **communicate** (for already-solved asks that just need surfacing), or **defer**. Below it, a **request detail table** lists each request with its severity, scope, and triage call, and the takeaways name the **top 3 to prioritise**. Open the result in the side panel to read the full breakdown and the mentions behind each row. ## Gotchas [#gotchas] * **The pattern pipeline is asynchronous and may charge credits.** It polls for several minutes. The assistant reuses existing pattern assignments on the dataset when they're present, so it only pays the full cost on the first run; re-running over the same data is faster. * **Feature requests are not pain points or objections.** The skill deliberately scopes to product-directed asks ("add X", "support Y"). Problems with the existing product are pain points; reasons people don't buy are objections. If you want those, use the matching skills instead — see Next steps. * **Already-solved asks are a discoverability signal, not a build.** When people request something you already ship, the skill triages it as `communicate` rather than `ship` — the work is surfacing the feature, not building it. * **You need collected mentions first.** Pattern matching requires a dataset that already has posts and comments — run a collection before applying the skill. * **Applying the skill does not run it.** Attaching the chip only pins the instructions to the draft. The analysis (and any credit cost) happens when you send the message. ## Next steps [#next-steps] Surface the problems people hit with what you already ship. Turn a prioritised request into a structured content brief. Learn the general flow for applying any skill to a chat. # Analyse pain points (/tutorials/analyse-pain-points) If you run an e-bike DTC brand, you already half-know the complaints — range anxiety, brake feel, delivery delays, patchy after-sales support — but you can't tell which one is actually costing you sales. The **Analyze pain points** skill reads a dataset you've already collected and returns a ranked map: a severity hero chart, pain-point clusters weighted by **reach + intensity + recurrence** (not raw mention count), and three handles for what to do next — a messaging fix, a product fix, and a content angle. You point the assistant at your mentions, apply the skill, and read the result. ## Prerequisites [#prerequisites] * A dataset with a completed collection run, so there are posts and comments to analyse. See [Set up your project](/tutorials/set-up-your-project) and [Create a mention collection](/tutorials/create-a-mention-collection). * Enough volume for the clusters to mean something. A few dozen mentions will surface anecdotes; aim for **\~100+** mentions before you trust the severity ranking. ## Walkthrough [#walkthrough] You can run this two ways: apply the built-in skill (so the assistant follows the full pain-point pipeline every time), or just ask the assistant in plain language. Both end at the same Asset. ### Open a chat [#open-a-chat] Open a chat against the dataset that holds your e-bike mentions — either from `/chat` or from your project's chat. The assistant works over the mentions that dataset's runs have already collected, so make sure the collection has finished first. ### Open skills [#open-skills] In the chat composer, click the **Open skills** book icon (or type `/` at the start of an empty message) to pop the skills picker. It shows your **Favourite skills** with a **Browse all skills** footer. ### Browse all skills [#browse-all-skills] Click **Browse all skills** to open the full library. **Analyze pain points** lives under both **Content & positioning** and **Audience research**, and it's tagged as a suggested skill, so it often surfaces near the top. You can also type its name into **Search skills…**. ### Use this skill [#use-this-skill] Open the **Analyze pain points** row to read its instructions, then click **Use this skill** to attach it to the current chat draft. The modal closes and the skill appears as a chip in the composer. Attach your e-bike dataset as context if it isn't already, type a short prompt like `Analyse the pain points in this dataset`, and send. If you'd rather skip the library, you can just ask in plain language — `What pain points do e-bike buyers complain about most?` — and the assistant will run the same pipeline. There is no separate "Pain Points" button; the skill is a saved instruction block, not a one-click report. ### Let the assistant run the pattern pipeline [#let-the-assistant-run-the-pattern-pipeline] Once you send, the assistant runs the pattern-matching pipeline to discover and cluster the pain-point categories, weighing each cluster by reach, intensity, and recurrence rather than how often it's mentioned. This runs **asynchronously** and can take a few minutes. If the dataset already has pattern assignments from an earlier run, the assistant **reuses** them instead of re-detecting, so the answer comes back faster. ### Open in side panel [#open-in-side-panel] The assistant wraps its deliverable in an Asset. Inline you'll see a clickable reference card with a short summary; click it to **Open in side panel** and read the full breakdown. You'll get a **severity hero chart**, a cluster detail table (each pain point with its weight and the mentions behind it), and **three handles** — a messaging angle, a product angle, and a content angle — telling you what to do with the top clusters. ### (Optional) Narrow the mentions by Sentiment [#optional-narrow-the-mentions-by-sentiment] To read the raw evidence behind a cluster, open the mentions view and use the filter builder. Click **Add filter**, set the **Where** field to **Sentiment**, leave the operator as **is**, and pick **Negative**. Combine rows with **and** / **or**, and use **Reset** to clear. This narrows which mentions you're reading — the pain points themselves are still derived by the assistant, not by the filter. **Refining the underlying query.** If your dataset is pulling in the wrong conversations, fix it upstream in the collection's keyword query rather than in the filter. Operators are **AND, OR, NOT** plus **parentheses** for grouping and **"double quotes"** for exact phrases. The **default join between two keywords is OR** (broadens reach) — add **AND** to require co-occurrence, **NOT** to exclude. Operators are case-insensitive (shown uppercase). Examples: `"electric bikes" NOT kids`; `nike AND (running OR marathon)`; `rivian polestar lucid` (implicit OR). Sanity-check complex queries against the live mention-count estimate. ## Gotchas [#gotchas] * **There is no "Pain Points" button or asset type.** Pain points are auto-extracted by the **Analyze pain points** skill (or by asking in plain language) via the pattern pipeline. The result is shown in the generic Asset wrapper — `Pain Points` is a label the assistant uses internally, not a dedicated control. * **There is no "emotion" mention filter.** The filter builder supports only **Keyword**, **Sentiment** (**Positive** / **Negative** / **Neutral**), **Views**, **Likes**, **Comments**, **Engagement rate**, and custom parameters. A finer-grained emotion breakdown exists per mention as a display field only — it is never filterable. * **The pipeline is asynchronous and may charge credits.** Pattern/post-processing runs cost roughly 0.5 credit per mention and poll for several minutes. By default the skill reuses any existing pattern on the dataset to avoid re-running; it only re-detects when you explicitly ask. * **Clusters are weighted, not counted.** Ranking blends reach, intensity, and recurrence, so a loud complaint from a few high-reach posts can outrank a quiet one mentioned more often. Read the weight column, not just the row order. * **You need collected mentions first.** The pipeline needs a dataset that already has posts and comments — run a collection before applying the skill. * **The chat needs a user session.** Over MCP, `buzzabout__ask` requires OAuth/JWT auth; an API key alone returns `403` and cannot reach the assistant. ## Now analyse it [#now-analyse-it] The same dataset answers more than just "what's wrong." Apply another built-in skill to extract a different slice: Surface the product-directed asks buried in the same mentions. Turn the top pain points into a structured brief for content. See the full menu of built-in skills and how to apply them. # Pattern analysis (/tutorials/analyse-patterns) Pattern analysis is the engine under most of buzzabout's research. It's a technique, not a button: you point a natural-language question at a dataset and the assistant discovers an *emergent taxonomy* — it reads the posts, finds the recurring distinctions, and groups them into a short, ranked set of clusters along **one axis you define per run**. Ask a B2B SaaS project-management dataset *"what pain points do people raise?"* and you get clusters like onboarding friction, integrations, pricing objections, and missing reporting — each backed by the exact posts that say so. Ask a different question over the same posts and you get a different map. This page explains *what* pattern analysis is and what you can get from it. Running it is the easy part — it's a single question in a chat, covered at the bottom. ## What it can extract [#what-it-can-extract] The axis is open-ended: you choose it by how you phrase the question. The same technique surfaces, among others: * pain points * feature requests * objections * topics & themes * frequently asked questions * CTA types * hooks * tone of voice * communication / narrative style * emotions * intent * content categories Each run reads across **text and visual signals** in the posts, scores the recurring ones, and clusters them into a handful of clearly distinct, named categories — then assigns each post to the cluster it best matches, with the citations to prove it. **Technique vs. use case.** Pattern analysis is the *approach*. Tutorials like [Analyse pain points](/tutorials/analyse-pain-points), [Analyse feature requests](/tutorials/analyse-feature-requests), and [Spot trends](/tutorials/spot-trends) are *use cases* that run this same technique with a specific question. There's no separate "pain points" or "trends" engine underneath — it's one pattern pipeline pointed at a different axis. That's why the possibilities are effectively endless: any distinction you can describe in a sentence is an axis you can cluster on. ## Why it's useful [#why-its-useful] A dataset of thousands of posts is unreadable by hand. Pattern analysis collapses it into the handful of distinctions that actually recur — and, crucially, ranks them by how much they matter (reach × intensity × recurrence, not raw count), so a loud-but-rare complaint doesn't outweigh a quiet-but-common one. The output is a map you can act on: the themes to address, the language your audience uses, the objections to pre-empt, the gaps to fill — each traceable back to real mentions. ## How to run it [#how-to-run-it] There's no setup, no "Patterns" page, and no button — you just ask. Point it at a dataset that already has collected mentions (aim for \~100+ for meaningful clusters), open its chat, and ask a question that defines the axis you care about: * *"What pain points do people raise about project-management tools?"* * *"What kinds of calls to action show up in these posts?"* * *"What tones of voice do people use here?"* The assistant interprets it as a pattern question, runs a `Pattern detection` block in the conversation (asynchronous, \~4 minutes), and then summarises the discovered clusters — citing the underlying posts for any of them. Per-post assignments also show up as an optional `Pattern Detections` column on the dataset's canvas, so you can see exactly which cluster each mention landed in. Reading and re-reading results is free; only the original run charges credits.