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.
/api/v2/productsscope: readFreeList active products for the authenticated team, including the target segments each product sells to. Disabled product metadata is hidden unless explicitly requested.
Query parameters
| Parameter | Type | Description |
|---|---|---|
include_disabled | boolean= false | Include 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
| Parameter | Type | Description |
|---|---|---|
segment_targeting | string | "all" targets every company in the product's industry universe (target_segments is empty). "selected" narrows to the listed segments.allselected |
insurance_entity_type | string | The kind of company this segment targets.carrierreinsureragencymgabrokerageunclassified_intermediaryunknown |
company_ownership | string | "any" means no ownership constraint is set.publicprivateany |
intermediary_facts | object (optional) | Present for MGA/agency segments with saved source-backed selectors. geographic_presence, observed_states_any, market_access, line_segments, license_types, mga_authority, and distribution_models combine as OR within a facet and AND across facets on the same company. carrier_count_range counts distinct appointing carriers; appointment_count_range counts source appointment/capacity records, which can include multiple records for one carrier. Unknown counts qualify unless include_unknown_carrier_count or include_unknown_appointment_count is false. State observations are positive regulatory evidence, not exhaustive national coverage. No active surplus authority observed does not prove an admitted book, and carrier breadth does not prove captive versus independent ownership. |
carrier_facts | object | null | Present 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. MGA/agency selectors are described in the optional intermediary_facts envelope. Earlier appointment-only constraints retain their legacy contract.
Examples
curl https://dealsurface.com/api/v2/products \
-H "Authorization: Bearer $DEALSURFACE_API_KEY"/api/v2/products/:productIdscope: readFreeGet detailed information about a specific active product, including its target segments. Disabled product metadata is hidden unless explicitly requested.
Path parameters
| Parameter | Type | Description |
|---|---|---|
productIdrequired | integer | The product ID (positive integer). |
Query parameters
| Parameter | Type | Description |
|---|---|---|
include_disabled | boolean= false | Allow 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.
/api/v2/prospectsscope: readFreeList 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.
Rows withunavailable(analysis is not available for this company in the current results) carry null scores and rank after assessed and pending rows and before unassessed rows. It is not a low score. An explicitmin_deal_scoreormin_buying_signalexcludes them.
Products that use published generations return apublicationobject describing the result set as a whole: a paging token and any warnings (for example an update in progress). Echopublication.generation_idback asgeneration_idon later pages of the same list; a 409 with reasongeneration_unavailablemeans the token is stale, so restart from offset 0 without it.
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
| Parameter | Type | Description |
|---|---|---|
product_idrequired | integer | Product to get prospects for. |
min_deal_score | integer | Minimum deal score (1-5). Implies assessed-only: unscored rows (including unavailable) can't match a score floor.12345 |
min_buying_signal | float | Minimum buying signal score (0.0-1.0). Same assessed-only implication as min_deal_score. |
assessment_status | string list | Optional narrowing to one or more assessment buckets (comma-separated).assessedpending_scoreunassessedunavailable |
entity_type | string list | Match families whose companies include any of these insurance entity types (comma-separated). "unknown" matches unclassified companies.carrierreinsureragencymgabrokerageunclassified_intermediaryunknown |
ownership | string= any | Filter on family ownership. Distinct from include_private, which is an entitlement gate.publicprivateany |
include_private | boolean= true | Defaults to the team-entitled population. Set false for public-only results. The response population field explains the policy and rank scope; live team entitlement always controls access. |
min_premium | number | Minimum annual premium in USD. Applies per line of business when lines_of_business is supplied, otherwise to total all-lines premium. |
max_premium | number | Maximum annual premium in USD. Same per-line versus total rule as min_premium. |
lines_of_business | string list | Comma-separated NAIC line-of-business keys. Matches families writing ANY of them. The full 30-key filterable vocabulary is printed below. |
min_licensed_states | integer | Minimum family-union licensed-state footprint (0-50). |
states_licensed_in | string list | Comma-separated two-letter state codes. Matches families licensed in ALL of them. The 50-state vocabulary is printed below. |
q | string | Name 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_children | boolean= false | Inline each family's subsidiaries with the list rows (same shape as hierarchy.children). Forces limit to 50 or fewer. |
sort_by | string= relevance | Sort key. relevance (default) is the fixed assessed-first ordering above. premium_line requires exactly one lines_of_business value (400 otherwise). complaint_index sorts by the all-lines index unless complaint_line (or a single supported CIS lines_of_business value) selects a line. 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 |
complaint_line | string | One supported CIS policy line. Selects the line used by sort_by=complaint_index, min_complaint_index and min_complaints. Defaults to a single supported lines_of_business value; sector roll-ups and statement-only lines retain the all-lines index. Unsupported explicit keys return 400. Per-line requests return 503 with Retry-After until a full projection refresh publishes the data. |
min_complaint_index | number | Minimum Complaint Index (at least this value) on the selected line. Strict: families with no index for that line are excluded and counted in excluded_no_data_groups. |
min_complaints | number | Minimum complaint count in the selected line (default 0). Use 10 to drop indexes built on one or two complaints. |
sort_dir | string | Sort direction; defaults to the sensible direction per key (descending for scores, premium, complaint index, and footprint).ascdesc |
limit | integer= 100 | Max results to return (1-500). |
offset | integer= 0 | Pagination offset. |
generation_id | string | Paging token (format ^g[0-9a-f]{15}$): echo publication.generation_id from the first page back unchanged on later pages of the same list. If results were updated in between, the request returns 409 reason=generation_unavailable: restart from offset 0 without generation_id. Sending it for a product without a publication object also returns 409. |
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_compensationNAIC 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_linesState vocabulary (the 50 US states)
AKALARAZCACOCTDEFLGAHIIAIDILINKSKYLAMAMDMEMIMNMOMSMTNCNDNENHNJNMNVNYOHOKORPARISCSDTNTXUTVAVTWAWIWVWYLicensed-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",
"by_line_of_business": {
"commercial_auto": {
"report_year": 2024,
"total_complaints": 41,
"complaint_index": 1.12,
"complaint_index_status": "available",
"contributing_entity_count": 3,
"low_sample": false
}
}
}
}
],
"total_groups": 412,
"total_entities": 1130,
"excluded_no_data_groups": 27,
"limit": 100,
"offset": 0
}Response fields
| Parameter | Type | Description |
|---|---|---|
assessment_status | string | assessed: analysis and score exist. pending_score: content indexed, score not yet computed. unassessed: no analysis yet. unavailable: analysis not available for this company in the current results. deal_score and buying_signal_score are null unless assessed.assessedpending_scoreunassessedunavailable |
rank / rank_total | integer | Position within this filtered result (rank_total equals total_groups by construction), not market standing. Not comparable across calls with different filters. |
entity_count | integer | Companies in this corporate family. |
matched_entity_count | integer | How many of them satisfied your filters. The returned ticker is the family's representative and may not be one of them. |
insurance_entity_types | string[] | Key omitted entirely when the family is unclassified — absence means "unknown"; an empty array would falsely claim "checked and found none". |
ownership | string | Family ownership.publicprivate |
licensed_state_count | integer | Cardinality of the family-wide union of licensed states, so it can exceed any single company's footprint. |
premium | object | null | Family 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. |
complaints | object | null | Family 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_status | string | available: 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 |
children | object[] | Only when include_children=true; same shape as hierarchy.children on the detail endpoint. |
total_groups | integer | Corporate families in the filtered universe — one per returned row across all pages. |
total_entities | integer | Companies inside those families. |
excluded_no_data_groups | integer | Families 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. |
publication | object | Only for products that use published generations; absent otherwise. Describes the result set as a whole. Fields: generation_id (paging token: echo it back on later pages; g000000000000000 means nothing has been published yet), state (published, empty_scope, no_successful_generation), update_in_progress, latest_update_failed, previous_settings, and warnings ({ code, message }[]; whole-result-set notes, never about one company). Never contains timestamps or counts. |
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
| Parameter | Type | Description |
|---|---|---|
400 | Bad Request | Invalid 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. |
404 | Not Found | Product not found, or not owned by your team. |
409 | Conflict | reason generation_unavailable: the paging token is stale (results were updated while you were paging, or generation_id was sent for a product without a publication object). Restart from offset 0 without generation_id. |
503 | Service Unavailable | The 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. |
/api/v2/prospects/:tickerscope: readFreeFull analysis for one company, plus firmographics, premium by line of business, complaint history, and its corporate family.
Path parameters
| Parameter | Type | Description |
|---|---|---|
tickerrequired | string | Company 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
| Parameter | Type | Description |
|---|---|---|
product_idrequired | integer | Product context for this prospect analysis. |
include_private | boolean= true | Defaults to ranking within the team-entitled population. False requests public-only ranking for public prospects; private prospects retain private-inclusive ranking. Detail ranks cover unfiltered families. |
report_year | integer | Narrow 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
| Parameter | Type | Description |
|---|---|---|
company_info | object | Firmographics 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. |
premium | object | null | This 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. |
complaints | object | null | Full 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. |
hierarchy | object | null | The 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_scope | string | Which grain the prose fields describe: "company" for this entity, "group" when the pipeline substitutes a parent-group narrative (as the UI does).companygroup |
excerpt_count | integer | How 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. Companies whoseassessment_statusis "unavailable" (analysis not available for this company in the current results) return the same facts-only shape; for them/excerptsand/outreachreturn 409 with reasonanalysis_unavailable.
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
| Parameter | Type | Description |
|---|---|---|
400 | Bad Request | Invalid ticker format or product_id. |
404 | Not Found | Unknown ticker for this product, or the alias is ambiguous. A known but unassessed or unavailable company is NOT a 404 — it returns the facts-only shape. |
503 | Service Unavailable | projection_unavailable — same body and semantics as the list endpoint. |
/api/v2/excerptsscope: readFreeSearch and filter evidence excerpts from filings and earnings calls for a prospect and product.
Query parameters
| Parameter | Type | Description |
|---|---|---|
product_idrequired | integer | Product the excerpts were assessed against. |
tickerrequired | string | Company serving alias (letters, numbers, dots, or hyphens; max 12 characters). Subsidiary aliases resolve like the detail endpoint. |
min_impact_score | integer | Minimum impact score (1-5).12345 |
keyword | string | Filter excerpts where pain_points contain this keyword (case-insensitive, max 200 chars). |
limit | integer= 50 | Max results (1-200). |
offset | integer= 0 | Pagination 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.
/api/v2/dealmapscope: readFreeRead 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
| Parameter | Type | Description |
|---|---|---|
product_idrequired | integer | Product context. |
tickerrequired | string | Company 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"/api/v2/dealmap/generatescope: readscope: outreach0-10 creditsGenerate or refresh the buying committee DealMap for a prospect. Maps the org structure and assigns stakeholder roles using AI analysis. Successful org_fresh and org_fresh_empty results cost 10 credits, including fresh empty org results supplemented with people. org_cache and annotated_cache_hit are free. Hard-capped at 20 requests per minute per API key; for many companies, run up to 10 generations in parallel.
Request body
| Parameter | Type | Description |
|---|---|---|
product_idrequired | integer | Product context. |
tickerrequired | string | Company serving alias. Subsidiary tickers resolve to the family; a generate that lands on a cached family map is not charged. |
force_refresh | boolean= false | Request a fresh org chart fetch, bypassing cached results and identity misses. Recent transient provider failures may temporarily defer refresh. Successful fresh results consume credits. |
force_refresh costs credits
Setting force_refresh: true requests a fresh org-chart fetch. Successful fresh results cost 10 credits, including empty org results supplemented with people. 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
| Parameter | Type | Description |
|---|---|---|
org_fresh | 10 credits | Fresh org-chart data was fetched. Charged. |
org_fresh_empty | 10 credits | Fresh org lookup was empty; the successful result includes supplemental people. Charged. |
org_cache | free | Cached org data was reused. Not charged. |
annotated_cache_hit | free | The cached annotated DealMap was returned. Not charged. |
Errors
| Parameter | Type | Description |
|---|---|---|
402 | Payment Required | Insufficient credits for a fresh generation. See Credits & Billing for the body shape. |
409 | Conflict | A DealMap generation for this prospect is already in flight. Wait, then read the result with GET /dealmap. Its body has no reason field. |
429 | Too Many Requests | Hard cap of 20 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"}'/api/v2/dealmap/node-rolescope: readFreeOverride 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
| Parameter | Type | Description |
|---|---|---|
product_idrequired | integer | Product context. |
tickerrequired | string | Company serving alias. |
node_idrequired | string | The node_id from the DealMap stakeholders. |
stakeholder_rolerequired | string | The 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"}'/api/v2/dealmap/node-top-pickscope: readFreeMark 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
| Parameter | Type | Description |
|---|---|---|
product_idrequired | integer | Product context. |
tickerrequired | string | Company serving alias. |
node_idrequired | string | The node_id from the DealMap stakeholders. |
is_top_pickrequired | boolean | true 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}'/api/v2/outreach/templatesscope: readscope: outreachFreeList active static messages and generation prompts for a product plus the built-in DealSurface default. Each entry reports its kind and credit_cost. The built-in is returned separately from paginated team templates.
| Parameter | Type | Description |
|---|---|---|
product_idrequired | integer | Product context. |
limit | integer= 50 | Maximum team templates (1-100). |
offset | integer= 0 | Team-template pagination offset. |
{
"system_template": {
"template_key": "system_default",
"name": "DealSurface Default",
"source": "system",
"kind": "generation_prompt",
"channel": "email",
"credit_cost": 1,
"version": 2,
"is_default": false
},
"effective_default_key": "550e8400-e29b-41d4-a716-446655440000",
"templates": [
{
"template_key": "550e8400-e29b-41d4-a716-446655440000",
"name": "Consultative CFO",
"description": "Metric-led outreach for finance leaders",
"kind": "generation_prompt",
"credit_cost": 1,
"version": 2,
"is_default": true,
"requires_lead": false
}
],
"pagination": { "offset": 0, "limit": 50, "total": 1, "has_more": false }
}/api/v2/outreachscope: readscope: outreach0–1 creditCreate outreach from a static message or generation prompt. Deterministic static messages are free; a generation prompt or static AI hook costs one credit on success. Hard-capped at 60 requests per minute per API key; for many leads, run up to 10 drafts in parallel.
Request body
| Parameter | Type | Description |
|---|---|---|
product_idrequired | integer | Product context. |
tickerrequired | string | Company serving alias. Subsidiary tickers resolve to the family's DealMap for lead selection. |
platformrequired | string | Target platform for the message.emaillinkedin |
selected_lead | SelectedLead | A full stakeholder object (from the DealMap response) to personalize the message for. Takes precedence over lead_selector. |
lead_selector | object | Selector 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). |
template_key | string | Key from GET /outreach/templates. Omit to use the product default; system_default explicitly selects the built-in template. |
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",
"used_excerpt_ids": [101, 102, 105],
"selected_lead": {
"full_name": "Jane Smith",
"title": "Chief Information Officer",
"level": "c_suite"
},
"selected_lead_note": null,
"template_used": {
"template_key": "550e8400-e29b-41d4-a716-446655440000",
"name": "Consultative CFO",
"kind": "generation_prompt",
"version": 2,
"source": "team",
"resolution": "product_default"
}
}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",
"lead_selector": { "level": "c_suite" }
}'