API Reference

Complete reference for every DealSurface REST API endpoint.

All endpoints require a Bearer token in the Authorization header. Rate-limit headers are included on every response. Filter vocabularies are closed: an unrecognized value in any enumerated input returns 400 naming the accepted set — it is never ignored, and never silently matches nothing.

GET/api/v2/productsscope: readFree

List active products for the authenticated team, including the target segments each product sells to. Disabled product metadata is hidden unless explicitly requested.

Query parameters

ParameterTypeDescription
include_disabledboolean= falseInclude disabled product metadata. Defaults to false.

Response

{
  "products": [
    {
      "product_id": 12,
      "name": "Cyber Insurance Platform",
      "description": "End-to-end cyber risk assessment...",
      "created_at": "2026-01-15T10:30:00Z",
      "is_active": true,
      "keywords": [
        { "keyword_id": 1, "name": "cyber risk", "auto_extracted": false }
      ],
      "sources": [
        { "source_id": 5, "url": "https://example.com", "title": "Product page", "source_type": "url" }
      ],
      "industries": [
        { "industry_name": "Fire, Marine & Casualty Insurance", "industry_description": "...", "industry_code": "6331" }
      ],
      "segment_targeting": "selected",
      "target_segments": [
        {
          "insurance_entity_type": "carrier",
          "company_ownership": "public",
          "carrier_facts": {
            "lines_of_business": [
              { "line_of_business": "commercial_auto", "premium_min": null, "premium_max": null, "unknown_premium": true },
              { "line_of_business": "general_liability", "premium_min": null, "premium_max": null, "unknown_premium": true }
            ],
            "union_premium": {
              "mode": "rank",
              "rank_high": 1,
              "rank_low": 250,
              "premium_min": null,
              "premium_max": null,
              "include_unknown": false
            },
            "licensed_footprint": {
              "min": null,
              "max": null,
              "bands": ["super_regional", "national"],
              "states_any": ["CA", "TX"]
            },
            "include_unknown": true
          }
        }
      ]
    }
  ]
}

Target segments

ParameterTypeDescription
segment_targetingstring"all" targets every company in the product's industry universe (target_segments is empty). "selected" narrows to the listed segments.
allselected
insurance_entity_typestringThe kind of company this segment targets.
carrierreinsureragencymgabrokerageunclassified_intermediaryunknown
company_ownershipstring"any" means no ownership constraint is set.
publicprivateany
carrier_factsobject | nullPresent only for carrier segments. lines_of_business combine as OR (a carrier qualifies by writing any one of them). union_premium is a separate condition measured across the union of those lines: mode "rank" carries rank_high/rank_low (1 = largest writer across the selected lines, ranked by summed premium), mode "gross" carries premium_min/premium_max in USD; include_unknown says whether carriers with no premium in any selected line still qualify. licensed_footprint carries either an exact { min, max } licensed-state-count range or ANY-match size bands (single = 1 state, regional = 2–12, super_regional = 13–34, national = 35+), never both, plus states_any — the carrier must be licensed in at least one of those states — AND-ed with the size condition; null when no footprint condition is set. The per-line premium_min/premium_max/unknown_premium fields carry the legacy per-line contract and read null / true for segments saved with union_premium.

Segments are read-only by design: they explain what a product targets so you can judge fit; changing them is a product-setup act that stays in the UI. Appointment-count constraints on agency/MGA segments exist in product setup but are not exposed in v2.0.

Examples

curl https://dealsurface.com/api/v2/products \
  -H "Authorization: Bearer $DEALSURFACE_API_KEY"
GET/api/v2/products/:productIdscope: readFree

Get detailed information about a specific active product, including its target segments. Disabled product metadata is hidden unless explicitly requested.

Path parameters

ParameterTypeDescription
productIdrequiredintegerThe product ID (positive integer).

Query parameters

ParameterTypeDescription
include_disabledboolean= falseAllow returning disabled product metadata. Defaults to false.

Response

Same shape as a single product from List Products, wrapped in { product: ... }.

Examples

curl https://dealsurface.com/api/v2/products/12 \
  -H "Authorization: Bearer $DEALSURFACE_API_KEY"

Returns 404 if the product doesn't exist or belongs to a different team, or if it is disabled andinclude_disabledis not true. Operational endpoints remain active-only.

GET/api/v2/prospectsscope: readFree

List prospects for a product, one row per corporate family, with premium and complaint data. Contains the full candidate universe: assessed and unassessed families alike, labeled by assessment_status.

Population and ordering

The list contains every company family in the product's universe, assessed or not, exactly like the web app. There is no population toggle; instead each row carries assessment_status, and unassessed rows have null scores. The default relevance ordering ranks assessed rows first (by internal absolute relevance score, then evidence strength and name, with a stable tiebreak), then unassessed rows — present and last, never hidden.

Filtering, sorting, ranking, and pagination are all applied before the response page is built, so rank and the totals always agree with the page you receive — a sorted first page really is the top of the whole filtered set, not the top of one page.

Query parameters

ParameterTypeDescription
product_idrequiredintegerProduct to get prospects for.
min_deal_scoreintegerMinimum deal score (1-5). Implies assessed-only: unscored rows can't match a score floor.
12345
min_buying_signalfloatMinimum buying signal score (0.0-1.0). Same assessed-only implication as min_deal_score.
assessment_statusstring listOptional narrowing to one or more assessment buckets (comma-separated).
assessedpending_scoreunassessed
entity_typestring listMatch families whose companies include any of these insurance entity types (comma-separated). "unknown" matches unclassified companies.
carrierreinsureragencymgabrokerageunclassified_intermediaryunknown
ownershipstring= anyFilter on family ownership. Distinct from include_private, which is an entitlement gate.
publicprivateany
include_privateboolean= falseInclude private and unknown-classification prospects when your team is entitled to private-company data. An entitlement gate, not an ownership filter.
min_premiumnumberMinimum annual premium in USD. Applies per line of business when lines_of_business is supplied, otherwise to total all-lines premium.
max_premiumnumberMaximum annual premium in USD. Same per-line versus total rule as min_premium.
lines_of_businessstring listComma-separated NAIC line-of-business keys. Matches families writing ANY of them. The full 30-key filterable vocabulary is printed below.
min_licensed_statesintegerMinimum family-union licensed-state footprint (0-50).
states_licensed_instring listComma-separated two-letter state codes. Matches families licensed in ALL of them. The 50-state vocabulary is printed below.
qstringName search over family name, ticker, aliases, and subsidiary names. q=Travelers Casualty returns the Travelers family row — useful when you were handed a name, not a ticker.
include_childrenboolean= falseInline each family's subsidiaries with the list rows (same shape as hierarchy.children). Forces limit to 50 or fewer.
sort_bystring= relevanceSort key. relevance (default) is the fixed assessed-first ordering above. premium_line requires exactly one lines_of_business value (400 otherwise). Any non-relevance key sorts on that value with the relevance tuple as tiebreak, so paging is deterministic. Rows missing the sort value sort last, never as zero.
relevancedeal_scorebuying_signalpremium_totalpremium_linecomplaint_indexlicensed_state_count
sort_dirstringSort direction; defaults to the sensible direction per key (descending for scores, premium, complaint index, and footprint).
ascdesc
limitinteger= 100Max results to return (1-500).
offsetinteger= 0Pagination offset.

Insurance filters are strict

A family with no premium data does not match a premium filter. On a product that targets more than one industry, applying an insurance filter therefore returns only the insurance prospects — that is the intended result, not data loss. excluded_no_data_groups counts the families dropped because they lacked the data a filter required, so thin data is visible rather than invisible.

Lines match any; states match all. “Writes commercial auto or homeowners” is the natural reading for lines. For states, “licensed in both TX and CA” is the question reps actually ask — nearly every carrier is licensed in at least one of any two states. (The web app uses any-match for states; the API deliberately diverges.)

Line-of-business vocabulary (43 filterable keys)

accident_healthaggregate_write_insaircraftallied_linesannuityboiler_machineryburglary_theftcommercial_autocommercial_multiple_perilcreditearthquakeexcess_workers_compensationfarmownersfederal_floodfidelityfinancial_guarantyfiregeneral_liabilitygroup_accident_healthgroup_annuitygroup_lifehomeownersindividual_accident_healthindividual_annuityindividual_lifeinland_marineinternationallifelong_term_caremedical_professional_liabilitymedicare_supplementmortgage_guarantymultiple_peril_cropocean_marinepet_insuranceprivate_cropprivate_floodprivate_passenger_autoproducts_liabilitysuretytitlewarrantyworkers_compensation

NAIC statement lines plus CIS policy-type lines (individual / group life, individual / group annuity, individual / group accident & health, medicare_supplement, long_term_care). The life, annuity and accident_health keys are roll-ups of their policy-type lines and remain filterable. The eight policy-type keys are populated only after the NAIC CIS re-ingest has run for an environment and may return empty results until then. One additional key appears in premium output but is rejected as a filter, because it is a report total rather than something a carrier writes:

total_all_lines

State vocabulary (the 50 US states)

AKALARAZCACOCTDEFLGAHIIAIDILINKSKYLAMAMDMEMIMNMOMSMTNCNDNENHNJNMNVNYOHOKORPARISCSDTNTXUTVAVTWAWIWVWY

Licensed-state data is validated against the 50 US states, so DC and the territories cannot appear in the data and are not accepted as filters — accepting them would let a filter silently match nothing. This is also what bounds min_licensed_states at 0–50.

Response

{
  "prospects": [
    {
      "ticker": "TRV",
      "company_name": "Travelers Companies Inc",
      "assessment_status": "assessed",
      "deal_score": 5,
      "buying_signal_score": 0.72,
      "rank": 1,
      "rank_total": 412,
      "entity_count": 34,
      "matched_entity_count": 6,
      "insurance_entity_types": ["carrier"],
      "ownership": "public",
      "lines_of_business": ["commercial_auto", "homeowners"],
      "licensed_states": ["CA", "NY", "TX"],
      "licensed_state_count": 50,
      "premium": {
        "currency": "USD",
        "report_years": [2023, 2024],
        "total_all_lines_usd": 32100000000,
        "by_line_of_business": { "commercial_auto": 4120000000 },
        "contributing_entity_count": 6
      },
      "complaints": {
        "report_years": [2024],
        "total_complaints": 412,
        "complaint_index": 0.87,
        "complaint_index_status": "available"
      }
    }
  ],
  "total_groups": 412,
  "total_entities": 1130,
  "excluded_no_data_groups": 27,
  "limit": 100,
  "offset": 0
}

Response fields

ParameterTypeDescription
assessment_statusstringassessed: analysis and score exist. pending_score: content indexed, score not yet computed. unassessed: no analysis yet. deal_score and buying_signal_score are null unless assessed.
assessedpending_scoreunassessed
rank / rank_totalintegerPosition within this filtered result (rank_total equals total_groups by construction), not market standing. Not comparable across calls with different filters.
entity_countintegerCompanies in this corporate family.
matched_entity_countintegerHow many of them satisfied your filters. The returned ticker is the family's representative and may not be one of them.
insurance_entity_typesstring[]Key omitted entirely when the family is unclassified — absence means "unknown"; an empty array would falsely claim "checked and found none".
ownershipstringFamily ownership.
publicprivate
licensed_state_countintegerCardinality of the family-wide union of licensed states, so it can exceed any single company's footprint.
premiumobject | nullFamily premium, deduplicated by NAIC company code. Null when none is reported. report_years lists every NAIC report year that contributed — more than one entry means the family sum blends years. See the warning below.
complaintsobject | nullFamily complaint summary; null when none. complaint_index is the ratio of the family's summed complaint shares to its summed premium shares (deduplicated by company code) — 1.0 is par, above 1.0 means more complaints than the family's premium footprint predicts. total_complaints sums member counts, each at its own latest year; report_years lists contributors.
complaint_index_statusstringavailable: an index was computed. no_premium_share: complaints were reported but no member has premium share, so no index exists — complaint_index is null and total_complaints is still meaningful. not_reported: no complaint data.
availableno_premium_sharenot_reported
childrenobject[]Only when include_children=true; same shape as hierarchy.children on the detail endpoint.
total_groupsintegerCorporate families in the filtered universe — one per returned row across all pages.
total_entitiesintegerCompanies inside those families.
excluded_no_data_groupsintegerFamilies excluded because an applied insurance filter had no data to evaluate. 0 when no insurance filter is applied. Never counts families that were evaluated and genuinely didn't match.

Premium is not year-aligned, and the total does not reconcile with the breakdown

total_all_lines_usd comes from NAIC CIS all-lines data at each company's own latest report year, so a family total can blend years; report_years lists every year that contributed. by_line_of_business comes from a different place: NAIC CIS per-line figures falling back to national direct-written-premium data, each at its own vintage.

The two are not reconcilable: total_all_lines_usd is not the sum of by_line_of_business. Roll-up keys (life, annuity, accident_health) also overlap their CIS policy-type splits when both are present — both premium blocks list those in rollup_lines_of_business; never sum a roll-up with its splits. Use the total for scale and the breakdown for line mix, and do not expect them to add up — the difference is source and vintage, not missing data.

Small carriers can top a complaint_index sort

The family complaint index only claims “more (or fewer) complaints than premium share predicts”. A small carrier can top a sort_by=complaint_index ranking on very little evidence. Corroborate with the detail endpoint's full per-line, per-year complaint series before treating an index as authoritative.

Examples

curl "https://dealsurface.com/api/v2/prospects?product_id=12&lines_of_business=commercial_auto&min_premium=50000000&states_licensed_in=TX,CA&sort_by=complaint_index" \
  -H "Authorization: Bearer $DEALSURFACE_API_KEY"

Errors

ParameterTypeDescription
400Bad RequestInvalid product_id, filter value, or pagination range. An unrecognized lines_of_business, states_licensed_in, entity_type, or sort_by value is always a 400 naming the accepted set. premium_line without exactly one lines_of_business value is also a 400.
404Not FoundProduct not found, or not owned by your team.
503Service UnavailableThe prospect data is rebuilding, or has not been rebuilt since the product's keywords changed. Body: { error: "projection_unavailable", reason: "rebuilding" | "stale_keywords", retry_after_seconds }. rebuilding is worth retrying in seconds; stale_keywords waits on a pipeline run, so do not poll tightly. In v1 these states returned an empty list.
GET/api/v2/prospects/:tickerscope: readFree

Full analysis for one company, plus firmographics, premium by line of business, complaint history, and its corporate family.

Path parameters

ParameterTypeDescription
tickerrequiredstringCompany serving alias (letters, numbers, dots, or hyphens; max 12 characters). Also accepts any hierarchy.children[].ticker from a previous response — family representatives and subsidiaries alike.

Query parameters

ParameterTypeDescription
product_idrequiredintegerProduct context for this prospect analysis.
include_privateboolean= falseRank this prospect within the private-inclusive population when your team is entitled. The prospect's identity — ticker, name, score, cached analysis — never changes.
report_yearintegerNarrow the premium and complaint series to a single report year. Omit it to receive every available year, which is the default — the trend is usually the actionable half: an index of 1.34 that was 2.10 two years ago is a different conversation.

Response

{
  "ticker": "TRV",
  "company_name": "Travelers Companies Inc",
  "assessment_status": "assessed",
  "deal_score": 5,
  "rank": 1,
  "rank_total": 412,
  "company_info": {
    "public_or_private": "public",
    "family_public_or_private": "public",
    "sic_codes": ["6331"],
    "insurance_entity_types": ["carrier"],
    "primary_naic_cocode": "19046",
    "all_naic_cocodes": ["19046", "25674"],
    "licensed_states": ["CA", "NY", "TX"],
    "insurance_types": ["Commercial Auto"],
    "website": "https://travelers.com",
    "primary_domain": "travelers.com"
  },
  "premium": {
    "currency": "USD",
    "report_years": [2024, 2023],
    "total_all_lines_usd": 28400000000,
    "by_line_of_business": { "commercial_auto": 4120000000 },
    "observations": [
      { "report_year": 2024, "line_of_business": "commercial_auto",
        "annual_premium_usd": 4120000000 },
      { "report_year": 2023, "line_of_business": "commercial_auto",
        "annual_premium_usd": 3980000000 }
    ]
  },
  "complaints": {
    "cocode": "19046",
    "available_years": [2024, 2023, 2022],
    "observations": [
      { "report_year": 2024, "line_of_business": "commercial_auto",
        "total_complaints": 118, "complaint_index": 1.34,
        "complaint_index_status": "available" },
      { "report_year": 2023, "line_of_business": "commercial_auto",
        "total_complaints": 96, "complaint_index": 1.09,
        "complaint_index_status": "available" }
    ]
  },
  "hierarchy": {
    "prospect_kind": "public_company",
    "child_count": 34,
    "eligible_child_count": 8,
    "coverage_ratio": 0.66,
    "group": { "ticker": "TRV", "company_name": "Travelers Companies Inc", "is_self": true },
    "children": [
      {
        "ticker": "TRVA1234",
        "company_name": "Travelers Casualty",
        "deal_score": 4,
        "has_assessment": true
      }
    ]
  },
  "narrative_scope": "company",
  "findings_summary": "Travelers is actively investing in...",
  "recommendation_summary": "Strong fit for cyber insurance...",
  "key_opportunity": "CEO cited cyber as top growth area...",
  "primary_challenge": "Legacy claims systems...",
  "impact_score_distribution": { "5": 12, "4": 30 },
  "findings": [
    {
      "finding_id": 1,
      "text": "Travelers announced a $200M technology...",
      "evidence_excerpt_ids": [101, 102]
    }
  ],
  "excerpt_count": 74,
  "excerpts": [
    {
      "excerpt_id": 101,
      "text": "We are investing significantly in...",
      "impact_score": 5,
      "source": { "name": "Q3 2025 Earnings Call", "date": "2025-10-28" },
      "pain_points": ["technology modernization", "cyber exposure"],
      "sales_summary": "CEO emphasizes cyber investment priority",
      "buying_signal_present": true
    }
  ]
}

Response blocks

ParameterTypeDescription
company_infoobjectFirmographics for this entity. insurance_entity_types is omitted entirely when unclassified — absence means "unknown", an empty array would falsely claim "checked and found none". primary_naic_cocode is this entity's own code when exactly one exists; all_naic_cocodes includes family-inherited codes and is not a superset duplicate. Like the premium and complaint rollups, it covers the whole corporate family — including members the hierarchy listing omits (hidden or entitlement-filtered); NAIC cocodes are public-registry identifiers.
premiumobject | nullThis ENTITY only, not the family. observations carry the year × line grain (same shape as complaints). The same non-reconciliation warning applies: total_all_lines_usd is not the sum of by_line_of_business — they come from different sources at different vintages; use the total for scale and the breakdown for mix.
complaintsobject | nullFull NAIC complaint series by year AND line for this entity's cocode, complete by default (narrow with report_year). complaint_index_status per observation: available, no_premium_share (complaints reported but no premium share that year, so no index — total_complaints is still meaningful), or not_reported.
hierarchyobject | nullThe corporate family: prospect_kind, child_count, eligible_child_count, coverage_ratio, the group representative, and children with their own tickers. has_assessment is per-product; where it is false, fetching the ticker returns the facts-only shape (null scores, empty findings and excerpts) rather than 404.
narrative_scopestringWhich grain the prose fields describe: "company" for this entity, "group" when the pipeline substitutes a parent-group narrative (as the UI does).
companygroup
excerpt_countintegerHow many excerpts exist in total; the embedded excerpts array is capped at 20.

Analysing a subsidiary

Pass any hierarchy.children[].ticker straight back to this endpoint — it accepts those aliases, not only the tickers /prospects returned. A subsidiary reports its family's rank rather than one of its own, and its own premium and complaint record. Where has_assessment is false, the company exists in the data but has no analysis for this product yet: fetching it returns the facts-only shape — assessment_status "unassessed", null scores, empty findings and excerpts, with company_info, premium, complaints, and hierarchy still populated from the data that does exist.

Embedded excerpts are capped at 20

The excerpts array carries at most 20, highest impact score first, with excerpt_count reporting how many exist. Use GET /excerpts for the rest. The cap exists because well-covered companies carry hundreds of excerpts — enough to exhaust an AI agent's context window in a single call. The complaint series stays complete by default: its observations are short scalars.

Examples

curl "https://dealsurface.com/api/v2/prospects/TRV?product_id=12" \
  -H "Authorization: Bearer $DEALSURFACE_API_KEY"

# Drill into a subsidiary using a hierarchy.children ticker
curl "https://dealsurface.com/api/v2/prospects/TRVA1234?product_id=12" \
  -H "Authorization: Bearer $DEALSURFACE_API_KEY"

Errors

ParameterTypeDescription
400Bad RequestInvalid ticker format or product_id.
404Not FoundUnknown ticker for this product, or the alias is ambiguous. A known but unassessed company is NOT a 404 — it returns the facts-only shape.
503Service Unavailableprojection_unavailable — same body and semantics as the list endpoint.
GET/api/v2/excerptsscope: readFree

Search and filter evidence excerpts from filings and earnings calls for a prospect and product.

Query parameters

ParameterTypeDescription
product_idrequiredintegerProduct the excerpts were assessed against.
tickerrequiredstringCompany serving alias (letters, numbers, dots, or hyphens; max 12 characters). Subsidiary aliases resolve like the detail endpoint.
min_impact_scoreintegerMinimum impact score (1-5).
12345
keywordstringFilter excerpts where pain_points contain this keyword (case-insensitive, max 200 chars).
limitinteger= 50Max results (1-200).
offsetinteger= 0Pagination offset.

framework_category is removed in v2

framework_category is gone as both a query parameter and a response field — here and in the excerpts embedded in prospect detail. It stopped being a meaningful axis when relevance filtering stopped gating on it. Filter with min_impact_score and keyword instead.

Response

{
  "excerpts": [
    {
      "excerpt_id": 101,
      "text": "We are investing significantly in...",
      "impact_score": 5,
      "source": { "name": "Q3 2025 Earnings Call", "date": "2025-10-28" },
      "pain_points": ["technology modernization"],
      "sales_summary": "CEO emphasizes cyber investment priority",
      "buying_signal_present": true
    }
  ],
  "total_count": 30,
  "limit": 50,
  "offset": 0
}

Examples

curl "https://dealsurface.com/api/v2/excerpts?product_id=12&ticker=TRV&min_impact_score=4&keyword=cyber" \
  -H "Authorization: Bearer $DEALSURFACE_API_KEY"

Subsidiary tickers resolve to the family

DealMaps are held at corporate-family grain. Passing a hierarchy.children[].ticker to any DealMap or outreach endpoint resolves upward to the family's DealMap and says so in the response — a 404 would falsely read as “this company has no buying committee”. A generate that resolves to an already-cached family map is not charged.

GET/api/v2/dealmapscope: readFree

Read the current buying committee DealMap for a prospect company and product. Returns stakeholders with AI-assigned roles, org hierarchy edges, and a buying committee summary.

Query parameters

ParameterTypeDescription
product_idrequiredintegerProduct context.
tickerrequiredstringCompany serving alias. Subsidiary tickers resolve to the family's DealMap.

Response

{
  "status": "ready",
  "chart": {
    "id": "abc-123",
    "generated_at": "2026-04-01T10:00:00Z",
    "expires_at": "2026-05-01T10:00:00Z",
    "company_name": "Travelers Companies Inc",
    "node_count": 45,
    "relevant_node_count": 12
  },
  "stakeholders": [
    {
      "node_id": "n-001",
      "full_name": "Jane Smith",
      "title": "Chief Information Officer",
      "level": "c_suite",
      "level_label": "C-Suite",
      "linkedin_url": "https://linkedin.com/in/janesmith",
      "work_email": null,
      "manager_name": null,
      "stakeholder_role": "Economic Buyer",
      "stakeholder_role_source": "generated",
      "annotation_score": 9,
      "annotation_reason": "Oversees technology budget...",
      "is_top_pick": true,
      "top_pick_source": "generated"
    }
  ],
  "edges": [
    { "from_node_id": "n-001", "to_node_id": "n-002", "relationship": "manages" }
  ],
  "buying_committee_summary": {
    "economic_buyer": "Jane Smith",
    "decision_maker": "Bob Johnson",
    "champion": null,
    "executive_sponsor": null,
    "evaluator": "Alice Wong",
    "blockers": []
  }
}

Returns "status": "empty" if no DealMap has been generated yet. Call POST /dealmap/generate first. "status": "stale" means a map exists but the annotation is past its TTL.

Examples

curl "https://dealsurface.com/api/v2/dealmap?product_id=12&ticker=TRV" \
  -H "Authorization: Bearer $DEALSURFACE_API_KEY"
POST/api/v2/dealmap/generatescope: readscope: outreach0-10 credits

Generate or refresh the buying committee DealMap for a prospect. Maps the org structure and assigns stakeholder roles using AI analysis. Credits charged only when fresh org-chart data is fetched. Hard-capped at 5 requests per minute.

Request body

ParameterTypeDescription
product_idrequiredintegerProduct context.
tickerrequiredstringCompany serving alias. Subsidiary tickers resolve to the family; a generate that lands on a cached family map is not charged.
force_refreshboolean= falseForce fresh org chart fetch, bypassing cache. Consumes credits.

force_refresh costs credits

Setting force_refresh: true always charges credits because it triggers a fresh org-chart fetch. Only use this if you need up-to-date organizational data and the cached results are known to be stale.

Response

{
  "status": "ready",
  "chart": {
    "id": "abc-123",
    "generated_at": "2026-04-01T10:00:00Z",
    "expires_at": "2026-05-01T10:00:00Z",
    "company_name": "Travelers Companies Inc",
    "node_count": 45,
    "relevant_node_count": 12
  },
  "stakeholders": [...],
  "edges": [...],
  "buying_committee_summary": {
    "economic_buyer": "Jane Smith",
    "decision_maker": "Bob Johnson",
    "champion": null,
    "executive_sponsor": null,
    "evaluator": "Alice Wong",
    "blockers": []
  },
  "generation_source": "org_fresh",
  "credits_charged": true,
  "credits_operation": "prospect_org_chart_annotation"
}

Credit behavior

ParameterTypeDescription
org_fresh10 creditsFresh org-chart data was fetched. Charged.
org_cachedfreeCached org data was reused. Not charged.
annotation_onlyfreeOnly the AI annotation was refreshed. Not charged.

Errors

ParameterTypeDescription
402Payment RequiredInsufficient credits for a fresh generation. See Credits & Billing for the body shape.
409ConflictA generation for this prospect is already in flight. Wait, then read the result with GET /dealmap.
429Too Many RequestsHard cap of 5 requests per minute exceeded.

Examples

curl -X POST https://dealsurface.com/api/v2/dealmap/generate \
  -H "Authorization: Bearer $DEALSURFACE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"product_id": 12, "ticker": "TRV"}'
PATCH/api/v2/dealmap/node-rolescope: readFree

Override the AI-assigned stakeholder role for a specific person on the buying committee DealMap. This is a team-wide override visible to all team members.

Request body

ParameterTypeDescription
product_idrequiredintegerProduct context.
tickerrequiredstringCompany serving alias.
node_idrequiredstringThe node_id from the DealMap stakeholders.
stakeholder_rolerequiredstringThe corrected stakeholder role.
Economic BuyerDecision MakerChampionExecutive SponsorEvaluatorEnd UserProcurement / Legal ReviewerBlockerIrrelevant

Response

{
  "success": true,
  "node_id": "n-001",
  "stakeholder_role": "Decision Maker"
}

Examples

curl -X PATCH https://dealsurface.com/api/v2/dealmap/node-role \
  -H "Authorization: Bearer $DEALSURFACE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"product_id": 12, "ticker": "TRV", "node_id": "n-001", "stakeholder_role": "Decision Maker"}'
PATCH/api/v2/dealmap/node-top-pickscope: readFree

Mark or unmark a stakeholder as the top pick for their role on the buying committee DealMap. Team-wide, like role overrides. Available since v1 but previously undocumented.

Request body

ParameterTypeDescription
product_idrequiredintegerProduct context.
tickerrequiredstringCompany serving alias.
node_idrequiredstringThe node_id from the DealMap stakeholders.
is_top_pickrequiredbooleantrue marks this stakeholder as the top pick for their role; false clears a manual top pick.

Response

{
  "success": true,
  "node_id": "n-001",
  "is_top_pick": true
}

The top pick per role feeds buying_committee_summary and the { stakeholder_role } lead selector on POST /outreach.

Examples

curl -X PATCH https://dealsurface.com/api/v2/dealmap/node-top-pick \
  -H "Authorization: Bearer $DEALSURFACE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"product_id": 12, "ticker": "TRV", "node_id": "n-001", "is_top_pick": true}'
POST/api/v2/outreachscope: readscope: outreach1 credit

Generate an evidence-backed outreach message for a prospect. Consumes 1 credit per successful request. Hard-capped at 10 requests per minute.

Request body

ParameterTypeDescription
product_idrequiredintegerProduct context.
tickerrequiredstringCompany serving alias. Subsidiary tickers resolve to the family's DealMap for lead selection.
platformrequiredstringTarget platform for the message.
emaillinkedin
tonerequiredstringDesired tone.
hard_sellhelpfulneutral
lengthstring= typicalDesired length.
shortertypicallonger
selected_leadSelectedLeadA full stakeholder object (from the DealMap response) to personalize the message for. Takes precedence over lead_selector.
lead_selectorobjectSelector to resolve a stakeholder from cached DealMap results. One of: { node_id: string }, { level: "manager"|"director"|"vp"|"c_suite" }, { full_name: string, level: string }, or { stakeholder_role: string } (picks the top-pick for that role).

Lead targeting

You can pass either selected_lead (the full lead object) or lead_selector (to resolve a lead from cached results). If both are provided, selected_lead takes precedence. If neither is provided, outreach is generated without personalization for a specific contact.

Response

{
  "subject": "Helping Travelers strengthen cyber risk assessment",
  "body": "Hi Jane,\n\nI noticed Travelers has been investing...",
  "platform": "email",
  "tone": "helpful",
  "length": "typical",
  "used_excerpt_ids": [101, 102, 105],
  "selected_lead": {
    "full_name": "Jane Smith",
    "title": "Chief Information Officer",
    "level": "c_suite"
  },
  "selected_lead_note": null
}

The used_excerpt_ids array indicates which evidence excerpts were used to ground the message. Cross-reference with the Excerpts endpoint to verify sources.

Examples

curl -X POST https://dealsurface.com/api/v2/outreach \
  -H "Authorization: Bearer $DEALSURFACE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "product_id": 12,
    "ticker": "TRV",
    "platform": "email",
    "tone": "helpful",
    "lead_selector": { "level": "c_suite" }
  }'
API Reference | DealSurface Docs