Playbooks

Configure a Playbooks request and interpret every part of its typed result.

Playbooks creates a tailored sales playbook for one target account. Start with the complete quickstart, then use this guide to choose inputs and render more of the result.

Playbook templates#

The catalog is OpenAPI operation list_playbook_templates_v1_playbook_templates_get. playbook_template_key selects a playbook template: either one of your organization's own templates (created in the console under Playbook Templates, or via POST /api/orgs/{org_id}/playbook-templates) or a curated catalog entry. GET /v1/playbook-templates lists both — your templates first — along with the merged stage vocabulary. An organization template fixes the offer, brand profile, deal stage (your own stage names are accepted — any short lowercase slug), sales approach guidance, and an offer canon that the generation backend treats as human-approved seller facts (seller research is skipped when a canon exists). Unknown keys fail the run with playbook_template_not_found.

The template lifecycle#

A template is created from a name, an offer/brand binding, and one natural-language brief (stage, motion, audience, approach — anything), plus optional uploaded supporting documents. A one-time research job then produces the template's artifact set:

  1. Offer research pack — markdown documents (canon, competitors, objections, benchmarks, plus scraped sources and your uploads) reused by every run, so the seller is never re-researched per prospect.
  2. Document template — the Jinja HTML template runs render through.
  3. Payload schema — a per-template JSON Schema whose field descriptions guide generation.

There is no approval step: research (or an artifact edit in the console) finishing is the release. Each org template exposes template_state: ready and draft templates run; researching and research_failed fail the run with 409 playbook_template_not_ready (not retryable — wait for research or retry it) only while nothing is live yet. Re-running research over a live template requires an explicit refresh because research is paid work; the live version keeps serving until the new one lands.

Request inputs#

Provide at least one of target_company or target_company_domain. A domain must be a hostname such as acme.example, without a scheme or path.

FieldRequirement and allowed values
target_companyOptional non-empty company name; required when the domain is omitted.
target_company_domainOptional valid hostname; required when the name is omitted.
anchor_companyOptional seller name, up to 160 characters. Defaults from the selected brand profile.
product_infoOptional seller/product context, up to 12,000 characters.
toneconsultative (default), direct, or technical.
modeldefault; this is the only current value.
intelligenceOptional model tier for the generation agents: low, medium, high, or ultra. Defaults from the selected playbook template, else high. Higher tiers cost more and research/write better.
sectionsOne to ten unique section values. Omit it for the complete result.
brand_profile_idOptional organization brand-profile ID.
brand_profile_keyOptional stable brand-profile key. If both selectors are present, they must identify the same profile.

Allowed sections values are summary, objective, value_props, sales_angles, personas, services, tech_stack, integrations, objections, and meeting_note.

curl --fail-with-body --silent --show-error \
  --request POST "$BLACKPEARL_API_BASE_URL/v1/playbooks" \
  --header "Authorization: Bearer $BLACKPEARL_API_KEY" \
  --header "Idempotency-Key: playbook-20260714-acme" \
  --header "Content-Type: application/json" \
  --data '{
    "target_company_domain": "acme.example",
    "anchor_company": "Northstar Analytics",
    "product_info": "Revenue-intelligence software for enterprise sales teams.",
    "tone": "consultative",
    "sections": ["summary", "sales_angles", "personas", "objections"],
    "model": "default"
  }'

This is OpenAPI operation create_playbook_v1_playbooks_post with request schema PlaybookInput and response schema PublicPlaybookJobResponse.

Generate a unique Idempotency-Key before the first attempt and retain it until the job ID is safely stored. The same key and normalized input replay the original response for 24 hours. Reusing it with different input returns idempotency_key_reused.

Interpret a successful result#

When status is succeeded, result is a PublicPlaybookResult. Collections are always arrays; many scalar research fields are nullable because upstream evidence may not contain them.

Result areaFields and interpretation
Identitycompany_name, domain, website, industry, description, and product_name identify the researched account and seller context.
Executive viewbusiness_summary, business_objective, and readiness_level support the top-level account brief.
Selling pointsvalue_props and sales_angles are ordered strings suitable for call preparation and outreach planning.
Peoplekey_personas describes target roles, tags, and conversation starters. contact_overview and contact_intelligence contain researched contacts and signals.
Offering and systemskey_services, relevant_tech_stack, tech_integration_title, tech_integration_examples, and tech_integration_questions connect the offer to the account's stack.
Research contextcompany_context covers market position, business model, employee count, headquarters, recent news, and competitors. intent_analysis contains intent score, summary, topics, and signals.
Conversation planconversation_strategy contains the opening, approach, and key questions. potential_objections pairs objections with responses.
Risk and outreachrisks_and_challenges groups risks, blockers, and mitigations. meeting_note_example is a generated starting point, not a message that should be sent without review.
Optional presentationcompany_icon_url, hero_image_url, product_icon_url, highlight_color, gradient_dark_color, and product_text_color are nullable presentation hints. Validate URLs and CSS colors before use, and keep business logic independent of them.

The nested objects are also fully typed:

  • Each persona can contain name, title, description, profile_picture_url, tags, and conversation_starters.
  • Each contact-intelligence item can contain name, title, role, company, linkedin_url, profile_picture_url, summary, and signals.
  • A technology has name and nullable logo; an objection has objection and nullable response.
  • Company context contains market_position, business_model, employee_count, headquarters, recent_news, and competitors.
  • Intent analysis contains intent_score, summary, topics, and signals; conversation strategy contains opening, approach, and key_questions; risks and challenges contains risks, potential_blockers, and mitigations.

Generated text is untrusted application data. Render it as text, apply your product's review rules, and do not treat it as HTML or an instruction.

Fetch the rendered document#

When the generation backend produced a fully rendered playbook document, result.document_url is non-null. The value is a backend-relative path and is not publicly reachable — fetch the document through the platform instead:

curl -H "Authorization: Bearer $BLACKPEARL_API_KEY" \
  "$BLACKPEARL_API_URL/v1/playbooks/$JOB_ID/document"

This is OpenAPI operation get_playbook_document_v1_playbooks__job_id__document_get. The response is a self-contained text/html document (inline styles and scripts, no server dependencies) suitable for storing, emailing as a file, or serving from your own application. Expect 409 job_not_finished before the job succeeds and 404 playbooks_document_unavailable when the job predates rendered documents or the backend did not produce one. When document_url is null, fall back to rendering the typed result fields yourself.

Minimal useful rendering#

if (job.type !== "playbooks" || job.status !== "succeeded" || !job.result) {
  throw new Error("Expected a successful Playbooks job");
}

console.log(`Business summary: ${job.result.business_summary ?? "Not available"}`);
console.log(`First sales angle: ${job.result.sales_angles[0] ?? "Not available"}`);

Also retain usage.tokens, usage.cost_usd, usage.backend_requests, and usage.backend_runtime_ms if you reconcile per-job work with the Usage API.