Enrichments

POST
/v1/companies/enrich/custom

Entity-first alias of POST /v1/companies/signals (Phase 2): the same research engine, cache, and price, returning the enrichment_run envelope instead of the bare signal.

Completion semantics

  • If the run is already complete when the request returns (served from the shared answer cache), the response is 200 with the result inline.
  • A fresh run returns 202 Accepted with Location: /v1/enrichment-runs/{runId} — poll that resource (or supply a webhookUrl). This surface never long-polls; callers who want blocking behaviour keep POST /v1/companies/signals/sync until its sunset.

Entity-first means the identifier is fixed by the route: domain is required and contactProfileUrl is rejected here.

Webhook payload: webhookUrl uses the same delivery path as /v1/companies/signals, so the callback body is the legacy signal shape (top-level answer, no run fields) — not the enrichment_run envelope. A run-shaped webhook payload arrives with the sunset plan (SEL-3685); until then, poll /v1/enrichment-runs/{runId} for the run shape.

Credits

Same pricing as POST /v1/companies/signals: 2 credits per run, repeat calls served from the cache do not consume additional credits, and failed runs are never charged. The run's credits block (on run reads) states what your organization was actually charged.

Authorization

ApiKeyAuth
AuthorizationBearer <token>

API key authentication using Bearer token. Format: sk_live_ followed by a secure random string.

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

domain*string

The company domain to research (e.g., "acme.com")

Match^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$
Length1 <= length <= 253
question*string

The research question to ask about the company

Length1 <= length <= 2000
answerType?string

The expected format of the answer

Default"open_text"

Value in

  • "open_text"
  • "number"
  • "boolean"
  • "list"
  • "percentage"
  • "currency"
  • "url"
  • "contacts"
  • "contacts_generation"
  • "contact_posts"
  • "contact_engagements"
  • "json_schema"
outputSchema?

JSON Schema defining the expected output structure. Required when answerType is "json_schema". The AI will generate data conforming to this schema.

Supported JSON Schema features:

  • type: object, array, string, number, integer, boolean, null
  • properties: Define object properties
  • required: List of required property names
  • items: Schema for array items
  • enum: Enumeration of allowed values
  • description: Property descriptions (helps AI generate better data)

Limitations:

  • Maximum 100 total properties
  • Maximum 5 levels of nesting
  • anyOf, oneOf, allOf are not supported
  • $ref only supports self-references for recursive schemas
webhookUrl?string

Optional webhook URL to receive notifications when processing completes. Webhooks are only sent for freshly processed signals — cached results do not trigger webhooks. Set forceRefresh: true to bypass the cache and ensure webhook delivery.

Formaturi
Lengthlength <= 2048
weight?string

The importance/weight of the signal

Value in

  • "important"
  • "nice_to_have"
  • "not_important"
qualificationCriteria?

Qualification criteria mapping answer values based on answerType. The structure must match the answerType specified.

For boolean answerType:

{  "yes": "good",  "no": "disqualified"}

For number, percentage, or currency answerType:

{  "ranges": [    { "rangeStart": 0, "rangeEnd": 100, "answerValue": "neutral" },    { "rangeStart": 101, "rangeEnd": 1000, "answerValue": "good" }  ]}

For list answerType:

{  "choices": {    "salesforce": { "answerValue": "good", "label": "Salesforce" },    "hubspot": { "answerValue": "neutral", "label": "HubSpot" },    "none": { "answerValue": "disqualified", "label": "No CRM" }  }}

Valid answerValue options (from worst to best):

  • disqualified: Answer indicates a deal-breaker or disqualifying factor
  • poor: Answer indicates a weak fit
  • neutral: Answer is acceptable but not ideal
  • good: Answer indicates a strong fit
  • excellent: Answer indicates an exceptional fit
signalTemplateId?string

Optional signal template ID to use. When provided, the template configuration (question, answerType, weight, qualificationCriteria) will be used. The system always resolves to the latest active version of the template. Only the domain parameter is required when using a template.

Formatuuid
forceRefresh?boolean

Force re-run of the signal analysis, skipping the cache. When set to true, a new signal will be created even if a cached result exists.

Defaultfalse
connectors?

Connector configuration for signal generation

verificationMode?string

Controls how strictly Saber verifies answers before responding.

  • strict: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.
  • lenient: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.
Default"strict"

Value in

  • "strict"
  • "lenient"

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/v1/companies/enrich/custom" \  -H "Content-Type: application/json" \  -d '{    "domain": "acme-corp.com",    "question": "Does this company use Kubernetes in production?",    "answerType": "boolean"  }'
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "object": "enrichment_run",  "entityType": "company",  "type": "string",  "status": "processing",  "entity": {    "domain": "string",    "companyId": "string",    "contactProfileUrl": "string"  },  "question": "string",  "signalTemplateId": "string",  "signalTemplateVersionId": "string",  "answerType": "string",  "result": {    "type": "open_text",    "openText": {      "value": "string"    }  },  "resultSchemaVersion": "string",  "reasoning": "string",  "confidence": 0,  "sources": [    null  ],  "qualificationCriteria": {},  "weight": "string",  "changeContext": {},  "credits": {    "state": "reserved",    "amount": 0,    "priceVersion": "string"  },  "error": "string",  "errorCode": "string",  "retryable": true,  "createdAt": "2019-08-24T14:15:22Z",  "completedAt": "2019-08-24T14:15:22Z"}
POST
/v1/companies/enrich/custom/batch

One shared question applied to a targets array of domains — one enrichment_run per target, no question × company expansion (unlike the legacy /v1/companies/signals/batch). Capped at 100 targets; larger workloads stay on the legacy async batch surface. Either question or signalTemplateId must be provided.

Webhook payload: webhookUrl uses the same delivery path as /v1/companies/signals, so each callback body is the legacy signal shape (top-level answer, no run fields) — not the enrichment_run envelope. A run-shaped webhook payload arrives with the sunset plan (SEL-3685).

Credits

2 credits per run (per target), success-only, cached results free — identical to single custom enrichment.

Authorization

ApiKeyAuth
AuthorizationBearer <token>

API key authentication using Bearer token. Format: sk_live_ followed by a secure random string.

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Either question or signalTemplateId must be provided; the shared config applies to every target.

targets*array<>

Company domains; one run per target.

Items1 <= items <= 100
question?string
Length1 <= length <= 500
answerType?string
Default"open_text"

Value in

  • "open_text"
  • "number"
  • "boolean"
  • "list"
  • "percentage"
  • "currency"
  • "url"
  • "contacts"
  • "contact_posts"
  • "contact_engagements"
  • "json_schema"
outputSchema?
webhookUrl?string
Formaturi
Lengthlength <= 2048
weight?string

Value in

  • "important"
  • "nice_to_have"
  • "not_important"
qualificationCriteria?
signalTemplateId?string
Formatuuid
forceRefresh?boolean
connectors?

Connector configuration applied to every run in the batch.

verificationMode?string

Value in

  • "strict"
  • "lenient"
generateSummaryOnComplete?boolean

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/v1/companies/enrich/custom/batch" \  -H "Content-Type: application/json" \  -d '{    "targets": [      "string"    ]  }'
{  "object": "enrichment_run_batch",  "accepted": 0,  "rejected": 0,  "runs": [    {      "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",      "object": "enrichment_run",      "entityType": "company",      "type": "string",      "status": "processing",      "entity": {        "domain": "string",        "companyId": "string",        "contactProfileUrl": "string"      },      "question": "string",      "signalTemplateId": "string",      "signalTemplateVersionId": "string",      "answerType": "string",      "result": {        "type": "open_text",        "openText": {          "value": "string"        }      },      "resultSchemaVersion": "string",      "reasoning": "string",      "confidence": 0,      "sources": [        null      ],      "qualificationCriteria": {},      "weight": "string",      "changeContext": {},      "credits": {        "state": "reserved",        "amount": 0,        "priceVersion": "string"      },      "error": "string",      "errorCode": "string",      "retryable": true,      "createdAt": "2019-08-24T14:15:22Z",      "completedAt": "2019-08-24T14:15:22Z"    }  ]}
POST
/v1/contacts/enrich/custom

Entity-first alias of POST /v1/contacts/signals: same engine, cache, and price, enrichment_run envelope on the wire. The identifier is fixed by the route: contactProfileUrl is required and domain is rejected here. Completion semantics match the company surface (200 inline on cache serve, 202 + Location for fresh runs).

Webhook payload: webhookUrl uses the same delivery path as /v1/contacts/signals, so the callback body is the legacy signal shape (top-level answer, no run fields) — not the enrichment_run envelope. A run-shaped webhook payload arrives with the sunset plan (SEL-3685); until then, poll /v1/enrichment-runs/{runId} for the run shape.

Credits

2 credits per run; cached repeats free; failed runs never charged.

Authorization

ApiKeyAuth
AuthorizationBearer <token>

API key authentication using Bearer token. Format: sk_live_ followed by a secure random string.

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

contactProfileUrl*string

The contact profile URL to research (e.g., "https://linkedin.com/in/johndoe")

Formaturi
Length1 <= length <= 500
question*string

The research question to ask about the contact

Length1 <= length <= 2000
answerType?string

The expected format of the answer

Default"open_text"

Value in

  • "open_text"
  • "number"
  • "boolean"
  • "list"
  • "percentage"
  • "currency"
  • "url"
  • "contacts"
  • "contacts_generation"
  • "contact_posts"
  • "contact_engagements"
  • "json_schema"
outputSchema?

JSON Schema defining the expected output structure. Required when answerType is "json_schema". See CreateSignalRequest.outputSchema for full documentation.

webhookUrl?string

Optional webhook URL to receive notifications when processing completes. Webhooks are only sent for freshly processed signals — cached results do not trigger webhooks. Set forceRefresh: true to bypass the cache and ensure webhook delivery.

Formaturi
Lengthlength <= 2048
weight?string

The importance/weight of the signal

Value in

  • "important"
  • "nice_to_have"
  • "not_important"
qualificationCriteria?

Qualification criteria mapping answer values based on answerType (same structure as company signals)

signalTemplateId?string

Optional signal template ID to use

Formatuuid
connectors?

Connector configuration for signal generation

verificationMode?string

Controls how strictly Saber verifies answers before responding.

  • strict: Only returns answers backed by verified sources (primary or trusted secondary sources). Returns null when information is unavailable.
  • lenient: Allows logical inference and best-effort estimates when direct evidence is missing. Uses industry benchmarks, logical correlates, and quantitative metrics.
Default"strict"

Value in

  • "strict"
  • "lenient"
forceRefresh?boolean

Force re-run of the signal analysis, skipping the cache. When set to true, a new signal will be created even if a cached result exists.

Defaultfalse

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/v1/contacts/enrich/custom" \  -H "Content-Type: application/json" \  -d '{    "contactProfileUrl": "https://www.linkedin.com/in/someone",    "question": "Has this person changed jobs in the last 6 months?",    "answerType": "boolean"  }'
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "object": "enrichment_run",  "entityType": "company",  "type": "string",  "status": "processing",  "entity": {    "domain": "string",    "companyId": "string",    "contactProfileUrl": "string"  },  "question": "string",  "signalTemplateId": "string",  "signalTemplateVersionId": "string",  "answerType": "string",  "result": {    "type": "open_text",    "openText": {      "value": "string"    }  },  "resultSchemaVersion": "string",  "reasoning": "string",  "confidence": 0,  "sources": [    null  ],  "qualificationCriteria": {},  "weight": "string",  "changeContext": {},  "credits": {    "state": "reserved",    "amount": 0,    "priceVersion": "string"  },  "error": "string",  "errorCode": "string",  "retryable": true,  "createdAt": "2019-08-24T14:15:22Z",  "completedAt": "2019-08-24T14:15:22Z"}
POST
/v1/companies/enrich/firmographics

Prebuilt enrichment: company profile, refined category, and customer segments — the same research contract as POST /v1/signals/firmographics, on the entity-first surface with the enrichment_run envelope and its completion semantics (200 inline on a cache serve, 202 + Location: /v1/enrichment-runs/{runId} for a fresh run). The completed result conforms to FirmographicsSignalAnswer.

Webhook payload: webhookUrl delivers the legacy signal shape (top-level answer, no run fields), and its namedSignal field carries the internal catalogue key (firmographics here; tech for tech-stack), not this surface's type vocabulary. The webhook moves to the run shape with the sunset plan (SEL-3685); until then poll /v1/enrichment-runs/{runId} for the public shape.

Credits

0.6 credits, charged once per result delivered to your organization. Repeat calls served from the cache are free — including results your organization already paid for via the legacy signals surface — and failed runs are never charged.

Authorization

ApiKeyAuth
AuthorizationBearer <token>

API key authentication using Bearer token. Format: sk_live_ followed by a secure random string.

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Request body for a prebuilt signal. You supply the company domain (plus optional delivery and refresh controls); the signal returns its own documented, structured answer.

domain*string

The company domain to research (e.g., "acme.com").

Match^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$
Length1 <= length <= 253
webhookUrl?string

Optional webhook to receive the completed signal. Intended for the async endpoints; if supplied on a synchronous /sync/ endpoint the webhook is still delivered, in addition to the blocking response.

Formaturi
Lengthlength <= 2048
forceRefresh?boolean

Force a fresh run. By default an identical request (same domain and parameters) returns the existing result instead of re-running; set this to true to bypass that and research again.

Defaultfalse

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/v1/companies/enrich/firmographics" \  -H "Content-Type: application/json" \  -d '{    "domain": "acme-corp.com"  }'
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "object": "enrichment_run",  "entityType": "company",  "type": "string",  "status": "processing",  "entity": {    "domain": "string",    "companyId": "string",    "contactProfileUrl": "string"  },  "question": "string",  "signalTemplateId": "string",  "signalTemplateVersionId": "string",  "answerType": "string",  "result": {    "type": "open_text",    "openText": {      "value": "string"    }  },  "resultSchemaVersion": "string",  "reasoning": "string",  "confidence": 0,  "sources": [    null  ],  "qualificationCriteria": {},  "weight": "string",  "changeContext": {},  "credits": {    "state": "reserved",    "amount": 0,    "priceVersion": "string"  },  "error": "string",  "errorCode": "string",  "retryable": true,  "createdAt": "2019-08-24T14:15:22Z",  "completedAt": "2019-08-24T14:15:22Z"}
POST
/v1/companies/enrich/funding

Prebuilt enrichment: a company's most recent funding and public/IPO status — the same research contract as POST /v1/signals/funding, on the entity-first surface with the enrichment_run envelope and its completion semantics (200 inline on a cache serve, 202 + Location: /v1/enrichment-runs/{runId} for a fresh run). The completed result conforms to FundingSignalAnswer.

Webhook payload: webhookUrl delivers the legacy signal shape (top-level answer, no run fields), and its namedSignal field carries the internal catalogue key (funding here; tech for tech-stack), not this surface's type vocabulary. The webhook moves to the run shape with the sunset plan (SEL-3685); until then poll /v1/enrichment-runs/{runId} for the public shape.

Credits

0.3 credits, charged once per result delivered to your organization. Repeat calls served from the cache are free — including results your organization already paid for via the legacy signals surface — and failed runs are never charged.

Authorization

ApiKeyAuth
AuthorizationBearer <token>

API key authentication using Bearer token. Format: sk_live_ followed by a secure random string.

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Request body for a prebuilt signal. You supply the company domain (plus optional delivery and refresh controls); the signal returns its own documented, structured answer.

domain*string

The company domain to research (e.g., "acme.com").

Match^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$
Length1 <= length <= 253
webhookUrl?string

Optional webhook to receive the completed signal. Intended for the async endpoints; if supplied on a synchronous /sync/ endpoint the webhook is still delivered, in addition to the blocking response.

Formaturi
Lengthlength <= 2048
forceRefresh?boolean

Force a fresh run. By default an identical request (same domain and parameters) returns the existing result instead of re-running; set this to true to bypass that and research again.

Defaultfalse

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/v1/companies/enrich/funding" \  -H "Content-Type: application/json" \  -d '{    "domain": "stripe.com"  }'
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "object": "enrichment_run",  "entityType": "company",  "type": "string",  "status": "processing",  "entity": {    "domain": "string",    "companyId": "string",    "contactProfileUrl": "string"  },  "question": "string",  "signalTemplateId": "string",  "signalTemplateVersionId": "string",  "answerType": "string",  "result": {    "type": "open_text",    "openText": {      "value": "string"    }  },  "resultSchemaVersion": "string",  "reasoning": "string",  "confidence": 0,  "sources": [    null  ],  "qualificationCriteria": {},  "weight": "string",  "changeContext": {},  "credits": {    "state": "reserved",    "amount": 0,    "priceVersion": "string"  },  "error": "string",  "errorCode": "string",  "retryable": true,  "createdAt": "2019-08-24T14:15:22Z",  "completedAt": "2019-08-24T14:15:22Z"}
POST
/v1/companies/enrich/mna

Prebuilt enrichment: a company's recent M&A activity, as acquirer or acquiree — the same research contract as POST /v1/signals/mna, on the entity-first surface with the enrichment_run envelope and its completion semantics (200 inline on a cache serve, 202 + Location: /v1/enrichment-runs/{runId} for a fresh run). The completed result conforms to MAndASignalAnswer.

Webhook payload: webhookUrl delivers the legacy signal shape (top-level answer, no run fields), and its namedSignal field carries the internal catalogue key (mna here; tech for tech-stack), not this surface's type vocabulary. The webhook moves to the run shape with the sunset plan (SEL-3685); until then poll /v1/enrichment-runs/{runId} for the public shape.

Credits

0.75 credits, charged once per result delivered to your organization. Repeat calls served from the cache are free — including results your organization already paid for via the legacy signals surface — and failed runs are never charged.

Authorization

ApiKeyAuth
AuthorizationBearer <token>

API key authentication using Bearer token. Format: sk_live_ followed by a secure random string.

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Request body for a prebuilt signal. You supply the company domain (plus optional delivery and refresh controls); the signal returns its own documented, structured answer.

domain*string

The company domain to research (e.g., "acme.com").

Match^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$
Length1 <= length <= 253
webhookUrl?string

Optional webhook to receive the completed signal. Intended for the async endpoints; if supplied on a synchronous /sync/ endpoint the webhook is still delivered, in addition to the blocking response.

Formaturi
Lengthlength <= 2048
forceRefresh?boolean

Force a fresh run. By default an identical request (same domain and parameters) returns the existing result instead of re-running; set this to true to bypass that and research again.

Defaultfalse

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/v1/companies/enrich/mna" \  -H "Content-Type: application/json" \  -d '{    "domain": "acme-corp.com"  }'
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "object": "enrichment_run",  "entityType": "company",  "type": "string",  "status": "processing",  "entity": {    "domain": "string",    "companyId": "string",    "contactProfileUrl": "string"  },  "question": "string",  "signalTemplateId": "string",  "signalTemplateVersionId": "string",  "answerType": "string",  "result": {    "type": "open_text",    "openText": {      "value": "string"    }  },  "resultSchemaVersion": "string",  "reasoning": "string",  "confidence": 0,  "sources": [    null  ],  "qualificationCriteria": {},  "weight": "string",  "changeContext": {},  "credits": {    "state": "reserved",    "amount": 0,    "priceVersion": "string"  },  "error": "string",  "errorCode": "string",  "retryable": true,  "createdAt": "2019-08-24T14:15:22Z",  "completedAt": "2019-08-24T14:15:22Z"}
POST
/v1/companies/enrich/open-jobs

Prebuilt enrichment: a company's current hiring — total open roles and example titles — the same research contract as POST /v1/signals/open-jobs, on the entity-first surface with the enrichment_run envelope and its completion semantics (200 inline on a cache serve, 202 + Location: /v1/enrichment-runs/{runId} for a fresh run). The completed result conforms to OpenJobsSignalAnswer.

Webhook payload: webhookUrl delivers the legacy signal shape (top-level answer, no run fields), and its namedSignal field carries the internal catalogue key (open-jobs here; tech for tech-stack), not this surface's type vocabulary. The webhook moves to the run shape with the sunset plan (SEL-3685); until then poll /v1/enrichment-runs/{runId} for the public shape.

Credits

0.5 credits, charged once per result delivered to your organization. Repeat calls served from the cache are free — including results your organization already paid for via the legacy signals surface — and failed runs are never charged.

Authorization

ApiKeyAuth
AuthorizationBearer <token>

API key authentication using Bearer token. Format: sk_live_ followed by a secure random string.

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Request body for a prebuilt signal. You supply the company domain (plus optional delivery and refresh controls); the signal returns its own documented, structured answer.

domain*string

The company domain to research (e.g., "acme.com").

Match^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$
Length1 <= length <= 253
webhookUrl?string

Optional webhook to receive the completed signal. Intended for the async endpoints; if supplied on a synchronous /sync/ endpoint the webhook is still delivered, in addition to the blocking response.

Formaturi
Lengthlength <= 2048
forceRefresh?boolean

Force a fresh run. By default an identical request (same domain and parameters) returns the existing result instead of re-running; set this to true to bypass that and research again.

Defaultfalse

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/v1/companies/enrich/open-jobs" \  -H "Content-Type: application/json" \  -d '{    "domain": "acme-corp.com"  }'
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "object": "enrichment_run",  "entityType": "company",  "type": "string",  "status": "processing",  "entity": {    "domain": "string",    "companyId": "string",    "contactProfileUrl": "string"  },  "question": "string",  "signalTemplateId": "string",  "signalTemplateVersionId": "string",  "answerType": "string",  "result": {    "type": "open_text",    "openText": {      "value": "string"    }  },  "resultSchemaVersion": "string",  "reasoning": "string",  "confidence": 0,  "sources": [    null  ],  "qualificationCriteria": {},  "weight": "string",  "changeContext": {},  "credits": {    "state": "reserved",    "amount": 0,    "priceVersion": "string"  },  "error": "string",  "errorCode": "string",  "retryable": true,  "createdAt": "2019-08-24T14:15:22Z",  "completedAt": "2019-08-24T14:15:22Z"}
POST
/v1/companies/enrich/tech-stack

Prebuilt enrichment: which system(s) a company currently uses — the same research contract as POST /v1/signals/tech, on the entity-first surface with the enrichment_run envelope and its completion semantics (200 inline on a cache serve, 202 + Location: /v1/enrichment-runs/{runId} for a fresh run). Provide exactly one of category (survey a category: erp or crm) or technology (verify one specific technology). The completed result conforms to TechSignalAnswer.

Webhook payload: webhookUrl delivers the legacy signal shape (top-level answer, no run fields), and its namedSignal field carries the internal catalogue key — tech, not this surface's tech-stack — so it is NOT a value the type vocabulary admits. The webhook moves to the run shape with the sunset plan (SEL-3685); until then poll /v1/enrichment-runs/{runId} for the public shape.

Credits

0.75 credits, charged once per result delivered to your organization. Repeat calls served from the cache are free — including results your organization already paid for via the legacy signals surface — and failed runs are never charged.

Authorization

ApiKeyAuth
AuthorizationBearer <token>

API key authentication using Bearer token. Format: sk_live_ followed by a secure random string.

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Request body for the Tech signal. Supply a domain plus exactly one of category or technology — providing neither or both is a 422.

Request body for the Tech signal. Supply a domain plus exactly one of category or technology — providing neither or both is a 422.

domain?string

The company domain to research (e.g., "acme.com").

Match^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$
Length1 <= length <= 253
category*string

A coarse technology category to survey. Mutually exclusive with technology.

Value in

  • "erp"
  • "crm"
technology?string

A specific technology to check for, given as free text (e.g. "salesforce", "SAP S/4HANA"); Saber resolves it to a canonical registry slug. An unresolvable value returns 422 with did-you-mean suggestions. Mutually exclusive with category.

webhookUrl?string

Optional webhook to receive the completed signal. Intended for the async endpoint; if supplied on the synchronous /sync/ endpoint the webhook is still delivered, in addition to the blocking response.

Formaturi
Lengthlength <= 2048
forceRefresh?boolean

Force a fresh run. By default an identical request (same domain and parameters) returns the existing result instead of re-running; set this to true to bypass that and research again.

Defaultfalse

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/v1/companies/enrich/tech-stack" \  -H "Content-Type: application/json" \  -d '{    "domain": "acme-corp.com",    "category": "crm"  }'
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "object": "enrichment_run",  "entityType": "company",  "type": "string",  "status": "processing",  "entity": {    "domain": "string",    "companyId": "string",    "contactProfileUrl": "string"  },  "question": "string",  "signalTemplateId": "string",  "signalTemplateVersionId": "string",  "answerType": "string",  "result": {    "type": "open_text",    "openText": {      "value": "string"    }  },  "resultSchemaVersion": "string",  "reasoning": "string",  "confidence": 0,  "sources": [    null  ],  "qualificationCriteria": {},  "weight": "string",  "changeContext": {},  "credits": {    "state": "reserved",    "amount": 0,    "priceVersion": "string"  },  "error": "string",  "errorCode": "string",  "retryable": true,  "createdAt": "2019-08-24T14:15:22Z",  "completedAt": "2019-08-24T14:15:22Z"}
POST
/v1/contacts/enrich/work-email

The email finder on the enrichment family (decision 16): the SAME lookup as POST /v1/contacts/find-email — identical request and response contract, same verification semantics, same 0.1-credit price. This rename is a name change, not a reprice. The legacy path keeps its 0.1-credit price until its announced sunset; use this route going forward.

See POST /v1/contacts/find-email for the full response semantics (status discriminator, catch-all handling, name parsing).

Credits

Each call charges 0.1 credits on success — both when a deliverable email is found and when no email is found (the verification attempt itself consumed work). Requests rejected before any verification work begins are not charged; insufficient balance returns 402 before any work is performed.

Authorization

ApiKeyAuth
AuthorizationBearer <token>

API key authentication using Bearer token. Format: sk_live_ followed by a secure random string.

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

full_name*string

Full name of the contact. Accepts 2-4 token names and European name particles (Joey van Ommen, Joey van der Ommen). Trailing credentials and honorifics (Ahmed Othman, MBA, CDMP, Dr. …), parenthetical roles, and mixed-script tails are cleaned off before parsing, so those resolve rather than being rejected. Non-Latin names (Arabic, Chinese, etc.) are transliterated and attempted best-effort as low-confidence parses. A value that is actually a company name (Best Vision Business Center) settles as not_found (not an error). A genuinely unparseable name — a single lone token, or empty — is rejected with 422.

Length1 <= length <= 200
domain*string

The mail domain for the contact's company. Accepts bare domains (saber.app), domains with www. prefix, and trailing-dot forms. URL-shaped inputs (https://saber.app) and email-shaped inputs (joey@saber.app) are rejected with 422.

If the domain redirects (a rebrand) or is dead (no mail), the finder resolves the company's real domain and searches there too — supplying company_name and/or location makes that discovery far more accurate.

Length1 <= length <= 253
company_name?string

Optional. The contact's company name. Used only when the supplied domain is dead or redirects: it anchors the web search that finds the company's real domain, and sharply reduces wrong-company matches. Strongly recommended whenever the domain may be stale.

Lengthlength <= 200
location?string

Optional. The contact's location (city / region / country). Enables location-aware candidate generation for global companies — e.g. a London contact at a firm with country domains resolves to the regional domain or subdomain.

Lengthlength <= 200

Response Body

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/v1/contacts/enrich/work-email" \  -H "Content-Type: application/json" \  -d '{    "full_name": "Joey Ommen",    "domain": "saber.app"  }'
{  "status": "found",  "email": "string",  "verification": {    "state": "deliverable",    "score": 0,    "accept_all": true  }}
GET
/v1/enrichment-runs

Unified list across company and contact runs. Filters are the common fields only: entityType, status, fromDate/toDate (against the run's creation time), exact entityId (the resolved company id — contact runs carry no entity id at rest, so an entityId filter never matches them), and type (custom or a prebuilt enrichment type). No per-entity attribute filters by design; look a company up by id, not by domain.

Reads are free. Each run carries a credits block stating what your organization was charged for it — none means it was served free (for example a cache hit).

Authorization

ApiKeyAuth
AuthorizationBearer <token>

API key authentication using Bearer token. Format: sk_live_ followed by a secure random string.

In: header

Query Parameters

entityType?string

Value in

  • "company"
  • "contact"
entityId?string

Exact company id; never matches contact runs.

type?string

Run type — custom or a prebuilt enrichment type.

Value in

  • "custom"
  • "firmographics"
  • "funding"
  • "mna"
  • "open-jobs"
  • "tech-stack"
status?array<>

Repeatable. Unknown values are rejected with 422.

fromDate?string

Include runs created at or after this instant.

Formatdate-time
toDate?string

Include runs created at or before this instant.

Formatdate-time
limit?integer
Range1 <= value <= 100
Default20
offset?integer
Range0 <= value
Default0

Response Body

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://example.com/v1/enrichment-runs"
{  "results": [    {      "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",      "object": "enrichment_run",      "entityType": "company",      "type": "string",      "status": "processing",      "entity": {        "domain": "string",        "companyId": "string",        "contactProfileUrl": "string"      },      "question": "string",      "signalTemplateId": "string",      "signalTemplateVersionId": "string",      "answerType": "string",      "result": {        "type": "open_text",        "openText": {          "value": "string"        }      },      "resultSchemaVersion": "string",      "reasoning": "string",      "confidence": 0,      "sources": [        null      ],      "qualificationCriteria": {},      "weight": "string",      "changeContext": {},      "credits": {        "state": "reserved",        "amount": 0,        "priceVersion": "string"      },      "error": "string",      "errorCode": "string",      "retryable": true,      "createdAt": "2019-08-24T14:15:22Z",      "completedAt": "2019-08-24T14:15:22Z"    }  ],  "total": 0,  "limit": 0,  "offset": 0,  "count": 0}
GET
/v1/enrichment-runs/{runId}

The run resource is the source of truth for async enrichment: poll it after a 202 (its path arrives in the Location header), or after a missed webhook. Free; includes the credits block.

Authorization

ApiKeyAuth
AuthorizationBearer <token>

API key authentication using Bearer token. Format: sk_live_ followed by a secure random string.

In: header

Path Parameters

runId*string
Formatuuid

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://example.com/v1/enrichment-runs/497f6eca-6276-4993-bfeb-53cbbbba6f08"
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "object": "enrichment_run",  "entityType": "company",  "type": "string",  "status": "processing",  "entity": {    "domain": "string",    "companyId": "string",    "contactProfileUrl": "string"  },  "question": "string",  "signalTemplateId": "string",  "signalTemplateVersionId": "string",  "answerType": "string",  "result": {    "type": "open_text",    "openText": {      "value": "string"    }  },  "resultSchemaVersion": "string",  "reasoning": "string",  "confidence": 0,  "sources": [    null  ],  "qualificationCriteria": {},  "weight": "string",  "changeContext": {},  "credits": {    "state": "reserved",    "amount": 0,    "priceVersion": "string"  },  "error": "string",  "errorCode": "string",  "retryable": true,  "createdAt": "2019-08-24T14:15:22Z",  "completedAt": "2019-08-24T14:15:22Z"}

On this page