# Public API Changelog

Observable changes to the public `/v1` API are recorded here. Internal refactors
that do not affect the public contract do not need an entry.

## Unreleased

- Jobs can no longer hang in `running` indefinitely. A job whose worker is lost
  (for example across a platform restart) is resumed automatically when its
  backend run can be safely re-attached; otherwise it transitions to `failed`
  with a retryable error message. Every job now also has a hard runtime
  ceiling (24 hours for `audiences`, 60 minutes for other types) after which it
  transitions to `failed`. Timed-out and lost runs are never charged. Terminal
  jobs now report wall-clock `duration_ms` in `usage`.

- Playbooks gain an optional `intelligence` tier (`low` | `medium` | `high` |
  `ultra`) selecting the generation agents' model class. Settable per run on
  `POST /v1/playbooks` and as a default on org playbook templates (used by
  their one-time research too); omitted runs inherit the template's tier,
  else `high` (the previous behavior).

- Org playbook templates become brief-driven artifact templates. Creation
  needs only a name, an offer/brand binding, and one natural-language `brief`
  (plus optional uploaded supporting documents); a one-time research job then
  produces the template's artifact set — an offer research pack (markdown
  docs + sources + uploads), a per-template Jinja document template, and a
  per-template payload JSON schema with per-field authoring guidance. There
  is no approval step: research (or an artifact edit) finishing is the
  release. `POST /v1/playbooks` fails with `409 playbook_template_not_ready`
  (not retryable) only while research is still running or failed with nothing
  live yet; a refresh over a live version never blocks runs. Org template
  objects expose `template_state`
  (`ready` | `researching` | `research_failed` | `draft`), `live_version`,
  `has_artifacts`, and `brief`. Hand-written canons are live from the start;
  templates created before artifact sets keep running on their canon.
- **Breaking:** removed runtime synthetic capability modes and the corresponding
  `*_STUB` configuration switches. Playbooks, Audiences, Prospecting, Documents,
  Account Research, CRM schema discovery, Brand Research, and Message Generation
  now call only their configured integration boundary; missing configuration fails
  closed instead of returning fabricated success. Transactional email is disabled
  as a true no-op when `SENDGRID_API_KEY` is absent, and card top-ups require Stripe.
- **Breaking:** removed the `_stub` response property and stub-only enum values from
  Account Coordination, Account Research, Company Dossier specifications, Data
  Intake specifications, Documents, Enrichment specifications, Message Templates,
  Professional Network specifications, Prospect Research, Prospecting,
   Qualification Policies, CRM schema snapshots, and Site Audit specifications.
   Connector verification metadata now reports `unavailable` rather than `stub` /
   `not_run`, and invoking an unsupported live verification returns `501`.
- Message Templates now supports asynchronous OpenAI-backed drafting through
  `POST /v1/message-template-revisions/generate` and evidence-bound prospect copy through
  `POST /v1/message-template-revisions/{revision_id}/prospect-message`. Both operations return a
  typed `message_templates` job, accept `Idempotency-Key`, remain approval-only, and never enqueue or
  send. Prospect results use `DRAFT_READY`, `NEEDS_INPUT`, `BLOCKED`, or `DEMONSTRATION_ONLY`; failed
  hard gates return no polished copy and make no provider request. For real recipients the platform,
  rather than the caller, verifies the operator-approved policy version, retained qualification and
  address provenance, and current Outcome-ledger suppression state. Personalization renders the
  selected immutable revision locally and validates the complete returned subject and body. Successful
  provider calls settle job result, revision, usage, debit, and cost rows through a durable billing
  outbox; usage exposes all token classes and true USD cost.
- **Breaking:** removed `GET /v1/prospecting/outcomes` and
  `POST /v1/prospecting/outcomes`. Record prospect-bound, account, and campaign
  feedback through `GET` and `POST /v1/outcomes`; there is no compatibility
  alias.
- Public stability is now classified per operation. GA operations cover
  platform core, Audiences, Brands, Offers, Campaigns, Outcomes, Playbooks,
  core Prospecting, Documents generation and download, and synchronous account
  research. Connector inventory, Prospecting continuations and qualification
  policies, Account Coordination, Company Dossier specifications, Data Intake,
  Enrichment, Message Templates, Professional Network, Prospect Research
  specifications, and Site Audit remain explicitly `x-stability: preview`.
- Added the GA Brand, Offer, Outcome, audience-snapshot, Playbook-template, and
  Prospecting event-stream operations, plus Campaign GTM-relation updates.
  Canonical guides and coverage now exist for every public operation.
- Added the Preview contract surfaces for connector inventory and the bounded
  specification resources listed above. These labels do not imply live
  provider execution; each Preview guide states its operational boundary.
- New `POST /v1/documents` generates finished sales-collateral files —
  proposals, playbooks, slide decks, GTM strategies, reports and one-pagers as
  `.pptx`/`.docx`/`.pdf` — as an asynchronous `documents` job driven by the
  Doc Maker managed agent. Send a `brief` plus optional `document_type`,
  `format`, `target_domain`, `brand`, `offer`, and up to three reference
  `template_urls` the agent follows for structure and tone; the platform
  composes the selling context from the brand's Brand Pack (the brand must
  have a completed guideline — `409 brand_guideline_missing` otherwise) and
  the attached Offer (`404 offer_not_found` / `409 offer_archived` when the
  selector is stale). The typed result references the stored files; download
  bytes with the new `GET /v1/documents/{job_id}/files/{file_id}`
  (format-specific content type, attachment filename, job retention window).
  Jobs appear in `GET /v1/jobs/{job_id}` as a typed `documents` variant with
  curated agent `progress`, and real runs meter and ledger the agent's actual
  model token cost in job `usage` and the cost ledger. The `documents`
  capability is GA and enabled for new organizations.
- New `POST /v1/account-research` returns one structured website-intelligence
  record for a company domain — identity, scale, market, digital presence
  (including `detected_tech`), buying signals and surfaced contacts —
  synchronously. Unknown domains return `404 account_not_found` unmetered, or
  send `analyze: true` to fetch and analyze the website on demand (the bebop
  app's on-miss flow; metered as one extra request, result carries
  `analyzed: true`, unfetchable sites return `422 account_unanalyzable`).
  Send `include_similar: true` to enrich the result with up to 10 lookalike
  companies (`similar_companies`, with similarity scores) from the research
  backend's similarity index. The `prospect_research` capability is GA and
  enabled for new organizations; each successful lookup meters one request.
- Org playbook templates gain a reviewed offer-canon workflow. Creating a
  template without a canon triggers a one-time offer research job; the
  researched canon must be reviewed and approved (in the console) before the
  template can run. While the canon is researching, awaiting review, or its
  research failed, `POST /v1/playbooks` fails with
  `409 playbook_template_not_approved` (not retryable — approve or edit the
  template's canon first). Approved canons are reused verbatim by every run;
  the seller is never re-researched per prospect. Org template objects in
  `GET /v1/playbook-templates` now include `review_status`
  (`approved` | `pending_review` | `researching` | `research_failed` | `draft`),
  `approved_version`, `pending_version_id`, `research_job_id`,
  `research_job_status`, and `research_error`. Templates created with a
  hand-written canon are approved from the start; existing templates with a
  canon were grandfathered as approved.
- `GET /v1/playbooks/{job_id}/document` now serves the platform-persisted copy
  of the rendered document; the generation backend is only proxied for jobs
  that predate artifact persistence. No response shape change.
- Playbook templates are now organization-defined. `playbook_template_key`
  accepts any org template key (created in the console) in addition to the
  curated catalog; unknown keys fail at run time with
  `playbook_template_not_found` instead of request validation. `stage` accepts
  any short lowercase slug — organizations define their own stage vocabulary —
  and `GET /v1/playbook-templates` now returns org templates ahead of the
  catalog with the merged stage list. An org template binds its offer and
  brand profile as defaults and forwards its focus, guidance, and offer canon
  to the generation backend.
- Playbooks results gain an optional `document_url` field: when the generation
  backend produced a fully rendered playbook document, `result.document_url`
  is non-null and the document can be fetched with the new
  `GET /v1/playbooks/{job_id}/document` operation (returns `text/html`;
  `404 playbooks_document_unavailable` when the job has no rendered document,
  `409 job_not_finished` before the job succeeds).
- Playbooks generation now forwards the resolved offer substance, stage
  template guidance, campaign frame, and requested sections to the generation
  backend, so backend offer templates can bind to platform Offers. No public
  request fields changed.
- `POST /v1/prospects` accepts a typed `mode` (`turbo` or `agentic`, default
  `turbo`). `turbo` streams the deterministic database-search pipeline —
  fastest to first prospect. `agentic` engages the bebop prospect agent, which
  plans the search, streams status updates, reasoning, and searches into the
  run's live thread (`GET /v1/prospects/{job_id}/events`), and delivers a
  curated prospect table. Agentic runs report real model cost: the agent's
  per-call token usage and its authoritative run total land in job `usage`
  and the cost ledger. When the agentic backend is unreachable the job fails
  with a clear error instead of returning fabricated results. The unreleased
  free-form `search_mode` input field was replaced by `mode`.
- Prospecting results now carry provider-derived qualification scores,
  validated-email confidence, and company firmographics (industry, headcount,
  location) when the backend supplies them.
- `GET /v1/usage` now returns a nullable `usage_tier` object (`key`, `name`,
  `rank`, `rate_limit_per_minute`, `monthly_cost_cap_usd`) and the
  `rate_limit.limit` value is now resolved from the organization's usage tier
  instead of a single global constant. Tiers grow automatically with
  cumulative paid usage and account age. Job creation adds a tier-based
  monthly spend cap to the existing admission gates: when the cap is reached,
  requests fail `429 budget_exceeded` with `error.details.scope` set to
  `"usage_tier"` (alongside `tier`, `limit_usd`, `spend_usd`, `resets_at`).
  As with budgets, do not retry until `resets_at` or the organization's tier
  changes.
- Audience cycles that run with `playbooks_enabled` now also register each
  generated playbook as a succeeded `playbooks` job in the same project,
  linked to the campaign via `campaign_id`. These appear in `GET /v1/jobs`
  and job listings alongside directly created playbooks; they do not consume
  playbook quota or record additional cost (the spend belongs to the cycle).
  Candidate `playbook_url` values no longer embed an access password;
  playbook access goes through the authenticated playbook endpoints.
- Audience runs now scale the entire backend pipeline (candidate retrieval,
  preliminary ranking, deep enrichment) to the requested `target_size` instead
  of only the final audience size. Small audiences complete faster and record
  proportionally lower cost; results for a given `target_size` are unchanged.
- `max_contacts_per_company` values up to the documented maximum of 10 are now
  honored; previously values above 5 were silently reduced to 5.
- Audience runs that fail, are canceled, or time out now record the downstream
  cost actually incurred before the terminal state. Failed runs may therefore
  show nonzero cost in `GET /orgs/{org_id}/costs` and the Costs page; prepaid
  credits are still only debited for succeeded runs.
- Public jobs now include `expires_at` and retain their input, result, and
  diagnostics for 30 days. Owned reads after expiry return `410 job_expired`;
  accounting and audit ledgers remain after payload cleanup.
- Authenticated responses now publish `RateLimit-Limit`,
  `RateLimit-Remaining`, and `RateLimit-Reset`. `GET /v1/usage` adds monthly
  boundaries, the rate policy, and applicable quota and budget counters.
  Accepted generation adds one usage request; completion adds tokens and cost
  without double-counting the request.
- Quota and budget `429` errors now include scope, current/remaining values, and
  the next UTC monthly reset in `error.details`. Cost quotas now reconcile to
  `cost_usd` usage correctly.
- Every public OpenAPI operation now carries an `x-stability` label. The public
  compatibility, preview, deprecation, and support-window policy is documented
  in [API compatibility and deprecation](https://api.blackpearl.com/docs/versioning).
- Public Playbooks, standalone Audience, and campaign-cycle job creation now
  accept `Idempotency-Key`. Keys are scoped to the authenticated project and
  operation; matching input replays the original response for 24 hours, while
  changed input returns `idempotency_key_reused`. Concurrent matching requests
  cannot create duplicate jobs and may transiently return
  `idempotency_in_progress` until the original response is stored.
- **Breaking (OpenAPI)**: public operations now use one HTTP Bearer security
  scheme instead of an optional per-operation `authorization` header parameter.
  Generated clients should configure the project API key once as a Bearer
  credential.
- **Breaking (typed jobs)**: `POST /v1/playbooks` now returns a
  Playbooks-specific typed job response, and `GET /v1/jobs/{job_id}` is a
  `type`-discriminated Playbooks-or-Audiences union. Stable public inputs,
  results, usage, candidates, campaigns, and usage rollups are now explicit
  schemas rather than free-form JSON objects.
- Public API errors now use a finite documented code catalog. Retry decisions are
  made by `error.code`: `rate_limit_exceeded` includes `Retry-After` and is
  transient, while `quota_exceeded` and `budget_exceeded` must be resolved by a
  reset or configuration change. Unexpected `/v1` failures return the same
  request-ID-bearing `ErrorResponse` envelope and documented 5xx responses.
- Added **Campaigns**: `POST /v1/campaigns` saves an audience-targeting campaign,
  `GET /v1/campaigns` / `GET /v1/campaigns/{campaign_id}` list and read them, and
  `POST /v1/campaigns/{campaign_id}/cycles` runs a campaign (a "cycle"), returning a
  standard job. `GET /v1/campaigns/{campaign_id}/cycles` lists a campaign's runs.
  Setup artifacts from the first cycle are reused, so later cycles skip the setup
  stages. Campaign creation supports a stable project-scoped `key`; cycle creation
  supports the standard `Idempotency-Key` header.
- **Breaking (audiences)**: a succeeded audiences job's `result` is now a compact
  summary (`count`, `result_shape`, score stats, `top_industries`, `icp`) and no
  longer embeds `candidates`. Fetch candidates from
  `GET /v1/audiences/{job_id}/candidates`, which now supports `q`, `sort`
  (`final_score`, `deep_match_score`, `company_name`, `full_name`), `direction` and
  cursor pagination over the full candidate set (no 500-row storage cap).
- `POST /v1/audiences` now creates a campaign and starts its first cycle; the
  returned job (and all jobs) includes a `campaign_id` field. Job `stages` entries
  are now a typed object (`key`, `label`, `status`).
- Removed capability-specific admission prices and all configured pre-run credit reserves.
  Usage-billed organizations may start work with any positive spendable balance. Actual metered
  cost plus markup is charged at settlement; when it exceeds the remaining balance, that balance
  is consumed and the asynchronous job fails with `insufficient_credits` without exposing its
  result. Auto-recharge continues to trigger from the resulting low balance.
- `GET /v1/usage` now returns a typed response including a `credits` object
  (`billing_enabled`, `balance_usd`, `topup_increment_usd`).

- OpenAPI `servers` and the `docs_url` in error responses now come from the
  gateway's `PUBLIC_API_BASE_URL` and `PUBLIC_DOCS_BASE_URL` settings instead of
  hard-coded hosts. Local development defaults to `http://localhost:8400` and
  `http://localhost:5173/docs`.
- API keys are issued in one standard type (`bp_live_` prefix). The console no
  longer offers a test/live mode selector, and a key prefix never changes
  execution semantics: every hosted call performs real work and records real
  usage. Existing keys, including legacy `bp_test_`-prefixed ones, continue to
  authenticate unchanged. Use separate development, staging, and production
  projects for isolation.
